Много раз нам нужно иметь возможность различать интерфейсы Typescript, которые имеют некоторые общие свойства. Возьмем следующий пример:
interface Vehicle { weight: number; wheels?: number; class?: 'A' | '1' | '2' | '3';
}
В нашем приложении у нас есть разные транспортные средства. Например, для грузовиков мы хотим отслеживать их вес и количество колес. Однако другие транспортные средства (например, яхты) не имеют колес, но имеют другие атрибуты, которые мы хотим знать, например, их класс, который представляет собой строку, которая может принимать значения «A», «1», «2» или «3». .
Мы могли бы подумать, что хорошая конфигурация может быть следующей:
interface Truck { weight: number; wheels: number;
} interface Yatch { weight: number; class: 'A' | '1' | '2' | '3';
} export type Vehicle = Truck | Yatch;
Однако с этой настройкой у нас возникнет проблема при попытке использовать Vehicle
объекты:
Как мы видим, Typescript не может решить, является ли входной объект Truck
или Yatch
, и выдает следующую ошибку:
Property 'wheels' does not exist on type 'Yatch'
Как мы можем сузить тип объекта? Есть как минимум 3 варианта:
1. Создайте новый type
свойство на каждом из интерфейсов
interface Truck { weight: number; wheels: number; type: 'truck';
} interface Yatch { weight: number; class: 'A' | '1' | '2' | '3'; type: 'yatch';
} export type Vehicle = Truck | Yatch;
Это известно как дискриминационные союзы в машинописном тексте. Это решение было бы наиболее адекватным, если бы наши интерфейсы уже имели type
атрибут заранее.
Когда используешь Vehicle
объектов, теперь мы можем использовать случай переключения, чтобы различать оба интерфейса:
Если мы используем классы вместо интерфейсов для наших типов, следующий альтернативный синтаксис дает точно такой же результат:
class Truck { weight: number; wheels: number; readonly type = 'truck';
} class Yatch { weight: number; class: 'A' | '1' | '2' | '3'; readonly type = 'yatch';
} export type Vehicle = Truck | Yatch;
2. Используйте защиту типа
Тип охранников — это функции, помогающие сузить тип.
interface Truck { weight: number; wheels: number;
} interface Yatch { weight: number; class: 'A' | '1' | '2' | '3';
} export type Vehicle = Truck | Yatch; export function isTruck(arg: any): arg is Truck { return !!arg.weight && !!arg.wheels;
} export function isYatch(arg: any): arg is Yatch { return !!arg.weight && !!arg.class;
}
If isTruck(object)
Возвращает true
, это означает, что наш объект действительно Truck
. Мы можем импортировать и использовать эти функции в любом месте нашего приложения:
Однако с этой настройкой есть небольшая проблема: мы все еще можем построить Vehicle
предметы, которых нет Truck
ни Yatch
:
3. Используйте «никогда»
Чтобы решить эту последнюю проблему, мы можем использовать домен never
тип, представленный в Typescript 2.0. Если интерфейс имеет свойство типа never
, то это свойство не может быть определено ни для одного объекта, следующего за этим интерфейсом.
interface Truck { weight: number; wheels: number; class?: never;
} interface Yatch { weight: number; wheels?: never; class: 'A' | '1' | '2' | '3';
} export type Vehicle = Truck | Yatch;
Ограничители шрифтов работают точно так же, как и раньше, но теперь мы не можем создавать Vehicle
предметы, обладающие одновременно wheels
и class
свойства:
Как мы видим, мы получаем следующую ошибку:
Type 'number' is not assignable to type 'never'
Проверьте испанскую версию этого поста в моем блоге cibetrucos.com.
Источник: https://www.codementor.io/davidsilvasanmartin/typescript-distributed-unions-1jhqp24mvx.