Typescript: сопоставьте тип объединения с одним типом

#typescript #types #typescript-typings #union-types

#typescript #типы #typescript-типизации #типы объединения

Вопрос:

Я пытаюсь сузить (без вывода) тип, который я хочу исключить из этого фильтра массива, но он выдает ошибку типа: «Item» отсутствуют следующие свойства

 type ItemList = (Item | ItemGroup )[];
type ItemGroup = {
  name: string;
  items: Item[];
}
type Item = {
  key: string;
  label: string;
}

const list: ItemList = [
   {
      key: 'key',
      label: 'label'
   },
   {
      name: 'name',
      items: [
        {
          key: 'key1',
          label: 'label2'
        },
        {
          key: 'key3',
          label: 'label4'
        },
      ]
   }
]

const groups: ItemGroup[] = list.filter( l => 'name' in l )
      ^^^^^^
// Type '(Item | ItemGroup)[]' is not assignable to type 'ItemGroup[]'.
//   Type 'Item | ItemGroup' is not assignable to type 'ItemGroup'.
//     Type 'Item' is missing the following properties from type 'ItemGroup': name, items ts(2322)

  

Есть идеи?

Комментарии:

1. похоже groups , что он должен быть введен как ItemList , потому filter что возвращает массив. Сообщение об ошибке на самом деле сообщает вам, в чем проблема: name для вашего нет ItemGroup .

2. извините, я пропустил, что когда я писал вопрос, он должен быть ItemGroup[] (обновляю его сейчас)

Ответ №1:

У вас есть массив, который содержит оба Item ItemGroup элемента и . Вы хотите отфильтровать этот массив только для тех элементов, которые есть ItemGroup , и вы хотите, чтобы typescript понимал, что вы отфильтровали список, и знал, что возвращаемый тип есть ItemGroup[] .

Как вы можете этого добиться, так это превратить фильтр l => 'name' in l в собственную функцию защиты типов. Возвращаемый тип value is ItemGroup сообщает typescript «тогда и только тогда, когда это верно, значение имеет тип ItemGroup».

 const isItemGroup = (value: Item | ItemGroup): value is ItemGroup => 'name' in value;

const groups: ItemGroup[] = list.filter( isItemGroup );
  

Используя защиту типа, typescript может понять значение list.filter , и ваша ошибка исчезнет.

Ссылка на игровую площадку

Комментарии:

1. Это также работает const groups = list.filter((value): value is ItemGroup => 'name' in value);

Ответ №2:

К сожалению, компилятор недостаточно умен, чтобы посмотреть на l => "name" in l обратный вызов и понять, что его можно использовать для сужения an Item | ItemGroup до простого an ItemGroup . К счастью, вы можете сообщить компилятору, что это намерение, аннотируя его как определяемую пользователем функцию защиты типа:

 const isItemGroup = (l: Item | ItemGroup): l is ItemGroup => "name" in l;
  

Теперь, если вы вызываете isItemGroup(l) и результат есть true , компилятор поймет, что l это an ItemGroup . Кроме того, стандартная библиотека предоставляет сигнатуру вызова Array.prototype.filter() , которая принимает определяемый пользователем обратный вызов типа guard и создает суженный массив. Таким образом, используя isItemGroup в качестве обратного вызова, вы получаете желаемый результат:

 const groups: ItemGroup[] = list.filter(isItemGroup); // no error now
  

Ссылка на игровую площадку для кода

Комментарии:

1. вы опередили меня в публикации на 38 секунд!

2. @LindaPaiste На секунду я подумал, что дважды опубликовал один и тот же ответ, поскольку они очень похожи. 🔮

3. Ах, хорошо, это имеет смысл. В то же время я использовал функцию уменьшения Lodash и просто проверял, что следующее значение содержит реквизит ‘name’. Мне нравится эта функция, спасибо!

Ответ №3:

Вы можете утверждать, что ваш отфильтрованный результирующий массив имеет тип ItemGroup[] , используя утверждение типа:

const groups: ItemGroup[] = list.filter( l => 'name' in l ) as ItemGroup[]