Все привет! Меня зовут Лихопой Кирилл и я - 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	

На этом руководство подошло к концу. Буду рад критике и отзывам в комментариях :)

Комментарии (1)


  1. XXLink
    12.01.2023 02:16

    Про сужение типов не согласен. Обычно код строят так, что либо то либо другое будет. А заводить доп поле type не очень хочется. Лучше для каждого типа свою функцию заводить.