Как ввести функцию, которая принимает различные типы, имеющие некоторые общие свойства

#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
}
 

и используйте его везде, где вы получите более общий тип, который вам нужно обеспечить, чтобы он был более конкретным.