Все привет! Меня зовут Лихопой Кирилл и я - fullstack-разработчик. В заключительной части руководства мы рассмотрим строгий режим и сужение типов в TypeScript. Эти техники позволяют точнее определять типы в коде, чтобы улучшить качество вашего кода и уменьшить количество багов.
Другие части:
Строгий режим в TypeScript
Рекомендуется включить все проверки типов в файле tsconfig.json
. После этого TypeScript будет выводить больше ошибок, однако эти ошибки помогут избежать множества багов в вашем приложении.
// tsconfig.json
"strict": true
Давайте обсудим несколько моментов, связанных со строгим режимом:
никаких any
и строгая проверка null
.
Никаких неявных any
В примере ниже TypeScript делает вывод, что параметр a
имеет тип any
. Как вы можете заметить, когда мы передаем числе в эту функцию и пытаемся вывести свойство name
ошибки не возникает. Это плохо.
function logName(a) {
// Почему нет ошибок?
console.log(a.name);
}
logName(97);
С включенной опцией noImplicitAny
TypeScript сразу же выдаст ошибку, если мы не укажем, какого типа должен быть a
:
// ОШИБКА: Параметр 'a' неявно имеет тип 'any'
function logName(a) {
console.log(a.name);
}
Строгая проверка null
Когда опция strictNullChecks
отключена, TypeScript игнорирует null
и undefined
. Это может привести к неожиданным ошибкам в работе кода.
Если мы включим опцию strictNullChecks
, null
и undefined
будут иметь собственный типы, и вы будете получать ошибку типа, если будете присваивать их переменным, которые ожидают определенный тип (например, string
).
const getSong = () => {
return 'song';
};
let whoSangThis: string = getSong();
const singles = [
{ song: 'bohemian rhapsody', artist: 'queen' },
{ song: 'yellow submarine', artist: 'the beatles' },
];
const single = singles.find(s => s.song === whoSangThis);
console.log(single.artist);
В примере выше нет гарантии, что singles.find
найдет песню, однако сейчас код написан так, как будто такого случая не будет.
Если же мы включим опцию strictNullChecks
, TypeScript будет выдавать ошибку, т.к. мы не гарантируем, что single
существует.
const getSong = () => {
return 'song';
};
let whoSangThis: string = getSong();
const singles = [
{ song: 'bohemian rhapsody', artist: 'queen' },
{ song: 'yellow submarine', artist: 'the beatles' },
];
const single = singles.find(s => s.song === whoSangThis);
console.log(single.artist); // ОШИБКА: возможно, объект 'undefined'.
По умолчанию, TypeScript говорит нам убедиться в том, что single
существует, до его использования. То есть сначала надо проверить, что оно не является null
или undefined
:
if (single) {
console.log(single.artist); // queen
}
Сужение типов в TypeScript
В TypeScript переменная может перейти от менее точного типа к более точному. Этот процесс называется сужением типов.
Ниже вы можете посмотреть пример, который показывает, как TypeScript сужает менее точный тип string | number
к более точному типу, когда мы используем условие с typeof
:
function addAnother(val: string | number) {
if (typeof val === 'string') {
// TypeScript обрабатывает 'val' как строку в этом блоке,
//поэтому мы можем использовать строковые методы, и TypeScript не выдаст ошибку
return val.concat(' ' + val);
}
// Здесь TypeScript уже знает, что 'val' - это число
return val + val;
}
console.log(addAnother('Супер')); // Супер Супер
console.log(addAnother(20)); // 40
Другой пример: у нас объявлен объединенный тип PlaneOrTrain
, который может быть типа Plane
или Train
.
interface Vehicle {
topSpeed: number;
}
interface Train extends Vehicle {
carriages: number;
}
interface Plane extends Vehicle {
wingSpan: number;
}
type PlaneOrTrain = Plain | Train;
function getSpeedRatio(v: PlaneOrTrain) {
// Здесь мы хотим вернуть отношение topSpeed/carriages или topSpeed/wingSpan
console.log(v.carriages); // ОШИБКА: 'carriages' не существует для типа 'Plane'
}
С тех пор, как getSpeedRatio
работает с несколькими типами, нам нужен способ различать, является ли v
типом Plane
или Train
. Мы можем сделать это, добавив для каждого типа отличительное свойство с литеральным строковым типом.
// У всех объектов типа Train теперь будет свойство со значением 'Train'
interface Train extends Vehicle {
type: 'Train';
carriages: number;
}
// У всех объектов типа Plane теперь будет свойство со значением 'Plane'
interface Plane extends Vehicle {
type: 'Plane';
wingSpan: number;
}
type PlaneOrTrain = Plain | Train;
Теперь мы можем использовать сужение типов TypeScript для v
:
function getSpeedRatio(v: PlaneOrTrain) {
if (v.type === 'Train') {
// Теперь TypeScript знает, что 'v' типа 'Train', поэтому сработало сужение и ошибки нет
return v.topSpeed / v.carriages;
}
// Если 'v' не типа 'Train', то сужение опять срабатывает и TypeScript знает, что здесь у 'v' тип 'Plane'
return v.topSpeed / v.wingSpan;
}
let bigTrain: Train = {
type: 'Train',
topSpeed: 100,
carriages: 20,
};
console.log(getSpeedRatio(bigTrain)); // 5
На этом руководство подошло к концу. Буду рад критике и отзывам в комментариях :)
XXLink
Про сужение типов не согласен. Обычно код строят так, что либо то либо другое будет. А заводить доп поле type не очень хочется. Лучше для каждого типа свою функцию заводить.