Если у вас есть класс с литералами, вы можете использовать это свойство для разметки частей объединения и их отличия.
В качестве примера рассмотрим объединение Square и Rectangle, здесь у нас есть элемент kind, который существует в обоих частях объединения и имеет определенный литеральный тип:
Если вы используете стиль проверки - защита типа, а именно сравнение (==, ===, !=, !==) или switch со свойством отличия (здесь kind), то TypeScript поймет, что объект должен иметь тип, который имеет этот конкретный литерал и уточнит для вас тип :)
1 2 3 4 5 6 7 8 9101112
functionarea(s:Shape){if(s.kind==='square'){// Теперь TypeScript *знает*, что `s` должен быть квадратом ;)// Так что вы можете безопасно использовать его свойства :)returns.size*s.size;}else{// Не квадрат? Что ж, TypeScript определит, что это должен быть// прямоугольник ;)// Так что вы можете безопасно использовать его свойства :)returns.width*s.height;}}
Довольно часто вы хотите убедиться, что у всех частей объединения есть какой-то код (действие) обрабатывающее их.
1 2 3 4 5 6 7 8 910111213141516171819
interfaceSquare{kind:'square';size:number;}interfaceRectangle{kind:'rectangle';width:number;height:number;}// Кто-то только что добавил этот новый тип `Circle`// Мы бы хотели, чтобы TypeScript выдавал ошибку везде где это *нужно*,// для её исправленияinterfaceCircle{kind:'circle';radius:number;}typeShape=Square|Rectangle|Circle;
В качестве примера того, где что-то идет не так:
123456789
functionarea(s:Shape){if(s.kind==='square'){returns.size*s.size;}elseif(s.kind==='rectangle'){returns.width*s.height;}// Было бы здорово, если бы вы могли заставить TypeScript выдавать// вам ошибку?}
Вы можете сделать это, просто добавив переход к следующему условию в списке(при отсутствии совпадения) и убедившись, что предполагаемый тип в этом блоке совместим с типом never. Например, если вы добавите тщательную проверку, вы получите красивую ошибку:
1 2 3 4 5 6 7 8 910
functionarea(s:Shape){if(s.kind==='square'){returns.size*s.size;}elseif(s.kind==='rectangle'){returns.width*s.height;}else{// ОШИБКА: `Circle` нельзя присвоить `never`const_exhaustiveCheck:never=s;}}
Это заставляет вас обработать этот новый вариант:
1 2 3 4 5 6 7 8 9101112
functionarea(s:Shape){if(s.kind==='square'){returns.size*s.size;}elseif(s.kind==='rectangle'){returns.width*s.height;}elseif(s.kind==='circle'){returnMath.PI*s.radius**2;}else{// Okay еще разconst_exhaustiveCheck:never=s;}}
При использовании strictNullChecks и выполнении тщательных проверок TypeScript может пожаловаться, что "не все пути кода возвращают значение". Вы можете отключить это, просто вернув переменную _exhaustiveCheck (типа never). Вот так:
Вы можете написать функцию, которая принимает never (и поэтому может быть вызвана только с переменной, которая логически выводится как never), а затем выбрасывает ошибку, если ее тело когда-либо выполняется:
12345
functionassertNever(x:never):never{thrownewError('Неожиданное значение. Не должно было быть never.');}
Пример использования с функцией вычисления площади:
1 2 3 4 5 6 7 8 910111213141516171819202122232425
interfaceSquare{kind:'square';size:number;}interfaceRectangle{kind:'rectangle';width:number;height:number;}typeShape=Square|Rectangle;functionarea(s:Shape){switch(s.kind){case'square':returns.size*s.size;case'rectangle':returns.width*s.height;// Если во время компиляции добавлен новый кейс, вы получите// ошибку компиляции// Если новое значение появится во время выполнения, вы получите// ошибку во время выполненияdefault:returnassertNever(s);}}
И после того, как у вас есть куча таких DTO, вы понимаете, что name было плохим выбором. Вы можете добавить управление версиями ретроспективно, создав новое объединение с литеральным номером (или строкой, если хотите) для DTO. Отметьте версию 0 как undefined, и если у вас включен strictNullChecks, это сработает:
1 2 3 4 5 6 7 8 9101112131415161718
typeDTO=|{version:undefined;// версия 0name:string;}|{version:1;firstName:string;lastName:string;}// Ещё позднее|{version:2;firstName:string;middleName:string;lastName:string;};// И так далее
import{createStore}from'redux';typeAction=|{type:'INCREMENT';}|{type:'DECREMENT';};/** * Это редюсер, чистая функция с сигнатурой (состояние, действие) => состояние . * Он описывает, как действие преобразует состояние в следующее состояние. * * Форма состояния зависит от вас: это может быть примитив, массив, объект, * или даже структура данных Immutable.js. Единственная важная часть - вы должны * не изменять объект состояния, а возвращать новый объект, если состояние * изменяется. * * В этом примере мы используем оператор switch и строки, но вы можете * использовать хелпер, который * следует другому соглашению (например, маппинг функций), если это имеет * смысл для вашего проекта. */functioncounter(state=0,action:Action){switch(action.type){case'INCREMENT':returnstate+1;case'DECREMENT':returnstate-1;default:returnstate;}}// Создаем хранилище Redux, в котором хранится состояние вашего приложения.// Его API: {subscribe, dispatch, getState}.letstore=createStore(counter);// Вы можете использовать subscribe() для обновления пользовательского// интерфейса в ответ на изменения состояния.// Обычно вы бы использовали библиотеку привязки представления (например,// React Redux), а не subscribe() напрямую.// Однако также может быть удобно сохранить текущее состояние в localStorage.store.subscribe(()=>console.log(store.getState()));// Единственный способ изменить внутреннее состояние - отправить действие.// Действия могут быть сериализованы, залогированы или сохранены, а затем// воспроизведены.store.dispatch({type:'INCREMENT'});// 1store.dispatch({type:'INCREMENT'});// 2store.dispatch({type:'DECREMENT'});// 1
Использование redux с TypeScript обеспечивает защиту от опечаток, повышенную способность к рефакторингу и самодокументируемый код.