TypeScript Выведение типов

Выведение типов — это способность 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
};