Typescript: шаблон объединения разных типов

Давайте поговорим об одном интересном шаблоне в Typescript, этот шаблон называется Discriminated Type Union
или Discriminated Union Type
. Прежде чем мы углубимся в эту тему, нам нужно понять проблему. Я напишу с вами код:
type PokemonState = {
status: "Loading" | "Success" | "Error";
pokemon?: { name: number; sprite: string; hp: number };
error?: { message: string };
};
Я создал тип под названием PokemonState
, который содержит status
, pokemon
с некоторыми свойствами и error
сообщением.
Теперь заметьте, что pokemon
и error
, зависит от статуса, поэтому может быть undefined
.
Если статус Loading
, просто верните статус.
Если статус Success
, просто верните покемона.
Если статус Error
, просто верните сообщение об ошибке.
Итак, давайте создадим функцию с включенным состоянием:
const printPokemon = (pokemonState: PokemonState) => {
switch (pokemonState.status) {
case "Loading":
console.log(pokemonState.status);
break;
case "Success":
console.log(pokemonState.pokemon.name, pokemonState.pokemon.sprite, pokemonState.pokemon.hp);
break;
case "Error":
console.log(pokemonState.error.message);
break;
default:
break;
}
};
Но я покажу то, что вижу в VSCode с проверкой Typescript:

Проверка безопасности типов Typescript уже здесь! Как я сказал, pokemon
и error
зависит от state
. Мы можем создать некоторую логику для нашей функции, проверяющую каждый статус, и определять, есть ли у нас наши свойства. Но это не лучшее решение.
Но теперь входит шаблон, называемый Dicriminated Type Union
. Давайте переделаем наш тип State
на три типа на основе нашего состояния.
type PokemonLoading = {
status: "Loading";
};
type PokemonSuccessState = {
status: "Success";
pokemon: { name: number; sprite: string; hp: number };
};
type PokemonErrorState = {
status: "Error";
error: { message: string };
};
Теперь мы можем удалить ?
cвойство может быть undefined
для каждого состояния. И давайте создадим наш тип объединения:
type PokemonNewState =
| PokemonLoading
| PokemonSuccessState
| PokemonErrorState;
И изменим тип параметра нашей функции:

const printPokemon = (pokemonState: PokemonNewState) => {
switch (pokemonState.status) {
case "Loading":
console.log(pokemonState.status);
break;
case "Success":
console.log(pokemonState.pokemon.name, pokemonState.pokemon.sprite, pokemonState.pokemon.hp);
break;
case "Error":
console.log(pokemonState.error.message);
break;
default:
break;
}
};
Мы применили шаблон к объединению разных типов. Теперь мы можем понять, почему этот шаблон вызывается с типом объединения.
Благодаря общему свойству мы можем разделить на другие типы и сделать Typescript безопасным для типов.
Мне нравится этот шаблон, и я просто поражаюсь тому, как Typescript может проверить и сделать наш код более чистым и понятным.