- Объяснять, какие проверки включает режим
strict - Получать точные литеральные типы из значений через
as const - Сравнивать
satisfiesс аннотацией типа и сas - Пользоваться пакетами
@typesи писать простой файл.d.ts
Ты уже знаешь типы, функции, интерфейсы, объединения, классы и дженерики. Этот последний урок — о четырёх инструментах, которыми профессионалы пользуются каждый день: строгий режим, который ловит большую часть ошибок; as const, чтобы получать точные типы из значений; satisfies, который проверяет тип, не расширяя его; и файлы объявлений, которые знакомят TypeScript с библиотеками JavaScript без типов.
Строгий режим
"strict": true в tsconfig.json — это не один параметр, а целый набор проверок. Без него TypeScript на многое закрывает глаза, и самые частые ошибки JavaScript проскальзывают. Вот важнейшие проверки из этого набора:
strictNullChecks—nullиundefinedстановятся отдельными типами; значение, которое «может бытьundefined», нельзя использовать без проверки.noImplicitAny— параметр, тип которого нельзя определить, не становится молчаany, а вызывает ошибку.strictPropertyInitialization— каждое поле класса должно получить значение при объявлении или в конструкторе.useUnknownInCatchVariables— вcatch (error)переменнаяerrorимеет типunknown, а неany.
const users = [
{ name: 'Aysel', age: 15 },
{ name: 'Murad', age: 17 },
];
const found = users.find((u) => u.name === 'Leyla');
console.log(found.age);users.ts:7:13 - error TS18048: 'found' is possibly 'undefined'.
7 console.log(found.age);
~~~~~find может ничего не найти. Без строгого режима код скомпилировался бы без ошибок, а при выполнении упал бы с TypeError: Cannot read properties of undefined (reading 'age').const users = [
{ name: 'Aysel', age: 15 },
{ name: 'Murad', age: 17 },
];
const found = users.find((u) => u.name === 'Leyla');
console.log(found?.age ?? 'not found');not found
?. и ??.as const
as const просит считать значение «замороженным»: строки получают точные литеральные типы вместо string, массив становится кортежем только для чтения, а свойства объекта — readonly. Это идеально подходит для фиксированных списков вроде ролей, статусов и настроек и удобно заменяет enum, о котором мы говорили раньше:
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number];
function canEdit(role: Role): boolean {
return role !== 'viewer';
}
const Status = { Active: 'active', Blocked: 'blocked' } as const;
type Status = (typeof Status)[keyof typeof Status];
const s: Status = Status.Blocked;
console.log(ROLES.join(', '), canEdit('editor'), s);admin, editor, viewer true blocked
Role — это 'admin' | 'editor' | 'viewer', а тип Status — 'active' | 'blocked'. Список существует и в программе: его можно вывести и перебрать в цикле.const ROLES = ['admin', 'editor', 'viewer'] as const;
ROLES.push('guest');roles.ts:2:7 - error TS2339: Property 'push' does not exist on type 'readonly ["admin", "editor", "viewer"]'.
2 ROLES.push('guest');
~~~~as const превращает массив в кортеж только для чтения, поэтому метода push у него нет.Оператор satisfies
Аннотация типа (const x: T = ...) проверяет значение, но расширяет тип переменной до T, и точные сведения о значении теряются. А satisfies T только проверяет: значение должно подходить под T, а переменная сохраняет точный тип, выведенный TypeScript. В палитре одни цвета — строки, другие — кортежи из трёх чисел:
type ColorName = 'red' | 'green' | 'blue';
type Color = string | [number, number, number];
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<ColorName, Color>;
console.log(palette.green.toUpperCase());
console.log(palette.red[0]);#00FF00 255
green — строка, а red — кортеж, потому что satisfies сохраняет точный тип.type ColorName = 'red' | 'green' | 'blue';
type Color = string | [number, number, number];
const annotated: Record<ColorName, Color> = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
};
annotated.green.toUpperCase();
const checked = {
red: [255, 0, 0],
green: '#00ff00',
bleu: [0, 0, 255],
} satisfies Record<ColorName, Color>;palette.ts:9:17 - error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
9 annotated.green.toUpperCase();
~~~~~~~~~~~
palette.ts:14:3 - error TS2353: Object literal may only specify known properties, and 'bleu' does not exist in type 'Record<ColorName, Color>'.
14 bleu: [0, 0, 255],
~~~~green — просто Color. Вторая: satisfies по-прежнему проверяет значение и ловит опечатку bleu.| Запись | Проверяет? | Тип переменной |
|---|---|---|
const x: T = value | да | T (расширяется) |
const x = value satisfies T | да | точный тип значения |
const x = value as T | почти нет | T |
Файлы объявлений и сторонние библиотеки
Файл, в котором есть только типы и нет исполняемого кода. Это «инструкция» к коду на JavaScript для TypeScript: какие функции существуют, какие параметры принимают и что возвращают.
Типы для npm-пакетов появляются одним из трёх способов. Многие современные пакеты поставляются вместе со своими файлами .d.ts, и делать ничего не нужно. Для других сообщество публикует типы в отдельных пакетах @types/.... Если нет ни того, ни другого, файл объявлений пишут самостоятельно.
npm install lodash
npm install --save-dev @types/lodashdependencies, а её типы — в devDependencies, потому что нужны только при разработке.Допустим, в проекте есть старая библиотека old-slug без типов — она превращает текст в короткое имя для URL. При её импорте TypeScript предупреждает, что не нашёл типы модуля:
src/index.ts:1:21 - error TS7016: Could not find a declaration file for module 'old-slug'. '/home/leyla/blog/node_modules/old-slug/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/old-slug` if it exists or add a new declaration (.d.ts) file containing `declare module 'old-slug';`
1 import slugify from 'old-slug';
~~~~~~~~~~Если пакета @types/old-slug нет, создаём файл types/old-slug.d.ts и описываем модуль через declare module. Слово declare означает, что этот код уже существует в другом месте, а мы лишь сообщаем его тип:
declare module 'old-slug' {
export default function slugify(text: string, separator?: string): string;
}types/old-slug.d.ts — сигнатура функции без тела, только типы.import slugify from 'old-slug';
const title = 'Hello from Baku';
console.log(slugify(title));
console.log(slugify(title, '_'));hello-from-baku hello_from_baku
src/index.ts теперь проходит проверку, редактор подсказывает параметры, а вызов slugify(42) дал бы ошибку TS2345.Когда публикуешь собственную библиотеку, писать .d.ts-файлы вручную не нужно. С параметром "declaration": true в tsconfig.json tsc создаёт рядом с каждым .js-файлом его .d.ts:
export interface User {
name: string;
age: number;
}
export declare function greet(user: User): string;greet.d.ts, созданный из greet.ts: остались интерфейс и сигнатура функции, а тело находится в greet.js.Главное
strict: trueвключаетstrictNullChecks,noImplicitAnyи другие проверки; держи его всегда включённым.asи!прячут сообщение об ошибке, но не решают проблему.as constделает из значения точные литеральные типы только для чтения;(typeof LIST)[number]выводит тип-объединение из списка.satisfies Tпроверяет значение поT, но сохраняет точный тип переменной.- Файлы
.d.tsсодержат только типы; типы берутся из самого пакета, из пакетов@types/...или из файла, который ты пишешь сdeclare module.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
"strict": true в tsconfig.json?