#typescript #typescript-generics
Вопрос:
Допустим, у меня есть 2 типа , которые имеют одно свойство, myProp
, которое является одним и тем же:
type TypeA = {
myProp: number;
propA: string;
}
type TypeB = {
myProp: number;
propB: string;
}
Я хочу создать функцию topMyProp
, которой может быть задан массив либо TypeA
или TypeB
и которая возвращает список элементов , значение myProp
которых превышает пороговое значение, например:
function topMyProp(items: any[], threshold: number) {
return items.reduce((acc, val) => {
if (val > threshold) acc.push(val);
return acc;
}, [])
}
Этот код работает, но не набран и теряет какое-либо представление об TypeA
или TypeB
.
Чтобы добавить информацию о наборе текста, я попытался добавить универсальный тип, подобный этому:
function topMyProp<T>(items: T[], threshold: number)
но это не работает, потому что это не сообщает о том, что T
должно быть myProp
в качестве собственности.
Я тоже пробовал это:
function topMyProp(items: {myProp: number}[], threshold: number)
но в этом случае topMyProp
возвращается {myProp: number}[]
, и я потерял информацию о том, были ли предметы TypeA
или TypeB
.
Как правильно добавлять типы в такой ситуации?
решение
Решение-это то, которое предложено комментарием @jonrsharpe
function topMyProp<T extends {myProp: number}>(items: T[], threshold: number) {
return items.reduce((acc, val) => {
if (val > threshold) acc.push(val);
return acc;
}, [] as T[])
}
Комментарии:
1. Можете ли вы наследовать свои типы?
2. @Йеппе Я не уверен, что наследование полностью решит проблему. Я могу сказать это
TypeA
иTypeB
использовать один и тот же унаследованный тип, но функция все равно вернет унаследованный тип, а не точный тип, используемый в качестве входных данных3.
T extends {myProp: number}
? Или извлеките базовый тип для расширения.4. Попробуйте этот tsplay.dev/WJ8grN
Ответ №1:
Ваша вторая попытка близка: функция должна знать, что будет передано как items
массив объектов, содержащих нужное вам свойство. Как предлагается в комментариях, вы можете использовать T extends {myProp: number}
для добавления информации, что объект будет содержать не только это свойство. Если вам в какой-то момент понадобится конкретная информация о том, какой именно тип, вы можете создать функцию защиты типа.
Альтернативой было бы написать интерфейс, описывающий общие свойства, и наследовать от него ваши типы:
interface MyProp {
myProp: number;
}
interface TypeA extends MyProp {
propA: string;
}
interface TypeB extends MyProp {
propB: string;
}
function topMyProp(items: MyProp[], threshold: number) {
// ...
}
Еще одной альтернативой было бы вместо этого использовать объединение типов TypeA | TypeB
, но все эти варианты обеспечивают одно и то же.
У вас не может быть аннотации, которая будет принимать более одного типа и при этом возвращать именно тот тип, который был использован, потому что Typescript является предварительным компилятором: код времени выполнения будет Javascript и не будет содержать информации о типах. По сути, вы просите TS предоставить вам функцию, которая принимает и возвращает как один, так и два типа одновременно.
Если вы хотите убедиться, что все, что выходит topMyProp()
, относится к определенному типу, вам придется создать защиту типа:
function isTypeA(obj: any): obj is TypeA {
return "PropA" in obj
}
и используйте его везде, где вы получите более общий тип, который вам нужно обеспечить, чтобы он был более конкретным.