Выведение типов — это способность TypeScript автоматически определять и присваивать типы переменным, возвращаемым значениям функций и выражениям на основе их использования и контекста, без необходимости явного указания аннотаций типов.
Эта мощная возможность сокращает избыточность кода, сохраняя при этом типовую безопасность.
Ключевые концепции
- Выведение типов: автоматическое определение типа по присвоенным значениям.
- Контекстное типизирование: типы выводятся из окружающего контекста.
- Наилучший общий тип: алгоритм поиска совместимого типа.
- Расширение/сужение типов: типы могут расширяться или ограничиваться в зависимости от использования.
- Когда происходит: инициализация переменных, возвращаемые значения, параметры по умолчанию, обратные вызовы, литералы.
Пример
// TypeScript выводит типы этих переменных
let name = "Alice"; // выведен как string
let age = 30; // выведен как number
let isActive = true; // выведен как boolean
let numbers = [1, 2, 3]; // выведен как number[]
let mixed = [1, "two", true]; // выведен как (string | number | boolean)[]
// Использование выведенных типов
name.toUpperCase(); // Работает, потому что name выведен как string
age.toFixed(2); // Работает, потому что age выведен как number
// name.toFixed(2); // Ошибка: свойство 'toFixed' не существует в типе 'string'
Выведение типа возвращаемого значения функции
TypeScript может определить тип возвращаемого значения функции на основе её оператора return.
Пример
// Тип возвращаемого значения выведен как string
function greet(name: string) {
return `Hello, ${name}!`;
}
// Тип возвращаемого значения выведен как number
function add(a: number, b: number) {
return a + b;
}
// Тип возвращаемого значения выведен как string | number
function getValue(key: string) {
if (key === "name") {
return "Alice";
} else {
return 42;
}
}
// Использование выведенных возвращаемых типов
let greeting = greet("Bob"); // выведен как string
let sum = add(5, 3); // выведен как number
let value = getValue("age"); // выведен как string | number
Контекстное типизирование
TypeScript способен выводить типы на основе контекста, в котором используются выражения.
Пример
// Тип параметра обратного вызова выводится из контекста метода массива
const names = ["Alice", "Bob", "Charlie"];
// Параметр 'name' выведен как string
names.forEach(name => {
console.log(name.toUpperCase());
});
// Параметр 'name' выведен как string, а тип возвращаемого значения — как number
const nameLengths = names.map(name => {
return name.length;
});
// nameLengths выведен как number[]
// Типы параметров в обработчиках событий также выводятся
document.addEventListener("click", event => {
// 'event' выведен как MouseEvent
console.log(event.clientX, event.clientY);
});
Выведение типов в объектных литералах
При работе с объектными литералами TypeScript также выводит типы свойств.
Пример
// TypeScript выводит тип этого объекта
const user = {
id: 1,
name: "Alice",
email: "alice@example.com",
active: true,
details: {
age: 30,
address: {
city: "New York",
country: "USA"
}
}
};
// Доступ к выведенным свойствам
console.log(user.name.toUpperCase());
console.log(user.details.age.toFixed(0));
console.log(user.details.address.city.toLowerCase());
// Ошибки типов будут обнаружены
// console.log(user.age); // Ошибка: свойство 'age' не существует в типе '...'
// console.log(user.details.name); // Ошибка: свойство 'name' не существует в типе '...'
// console.log(user.details.address.zip); // Ошибка: свойство 'zip' не существует в типе '...'
Продвинутые паттерны
Утверждения const
Пример
// Обычное выведение типов (расширяется до string)
let name = "Alice"; // тип: string
// Утверждение 'const' (сужает до литерального типа)
const nameConst = "Alice" as const; // тип: "Alice"
// С объектами
const user = {
id: 1,
name: "Alice",
roles: ["admin", "user"] as const // кортеж только для чтения
} as const;
// user.name = "Bob";
// Ошибка: нельзя присвоить значение свойству 'name', так как оно доступно только для чтения
Защитники типов и анализ потока управления
Пример
function processValue(value: string | number) {
// Тип сужается до string в этом блоке
if (typeof value === "string") {
console.log(value.toUpperCase());
}
// Тип сужается до number здесь
else {
console.log(value.toFixed(2));
}
}
// Дискриминированные объединения
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; size: number; }
type Shape = Circle | Square;
function area(shape: Shape) {
// Тип сужается на основе свойства 'kind'
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
}
}
Лучшие практики
Ниже — несколько рекомендаций по работе с выведением типов в TypeScript.
Пример
// 1. Позвольте TypeScript вывести простые типы
let message = "Hello"; // Хорошо: здесь не нужно явно указывать тип
// 2. Явно указывайте типы для параметров функций
function formatName(firstName: string, lastName: string) {
return `${firstName} ${lastName}`;
}
// 3. Рассмотрите возможность добавления аннотаций возвращаемого типа для сложных функций
function processData(input: string[]): { count: number; items: string[] } {
return {
count: input.length,
items: input.map(item => item.trim())
};
}
// 4. Используйте явные аннотации типов для пустых массивов или объектов
const emptyArray: string[] = []; // Без аннотации выведен как any[]
const configOptions: Record<string, unknown> = {}; // Без аннотации выведен как {}
// 5. Используйте утверждения типов, когда TypeScript не может вывести тип корректно
const canvas = document.getElementById("main-canvas") as HTMLCanvasElement;
Когда стоит использовать явные типы
Хотя выведение типов — мощная возможность, есть ситуации, когда рекомендуется явно указывать аннотации типов:
Рекомендуется использовать явные типы:
- Публичные контракты API: параметры и возвращаемые типы функций в библиотечном коде.
- Сложные типы: когда выведенный тип слишком широк или сложен.
- Документация: чтобы сделать код более самодокументируемым.
- Безопасность типов: когда нужно обеспечить конкретные ограничения.
- Пустые коллекции: пустые массивы или объекты, которые будут заполнены позже.
Соображения по производительности
Пример
// Хорошо: явный тип для сложных возвращаемых значений
function processData(input: string[]): { results: string[]; count: number } {
return {
results: input.map(processItem),
count: input.length
};
}
// Хорошо: явный тип для пустых массивов
const items: Array<{ id: number; name: string }> = [];
// Хорошо: явный тип для объектов конфигурации
const config: {
apiUrl: string;
retries: number;
timeout: number;
} = {
apiUrl: "https://api.example.com",
retries: 3,
timeout: 5000
};