Hack Frontend Community

Type Assertions в TypeScript

Что такое Type Assertions?

Type Assertions (Утверждения типов) — это способ сказать компилятору TypeScript: "Я знаю тип этого значения лучше, чем ты". Это не приведение типов и не изменяет значение во время выполнения — это только указание для компилятора.


Синтаксис

TypeScript поддерживает два синтаксиса для type assertions:

Синтаксис as

const value: any = "hello";
const length = (value as string).length;  // 5

Синтаксис угловых скобок

const value: any = "hello";
const length = (<string>value).length;  // 5

Важно:

В JSX/TSX можно использовать только синтаксис as, так как угловые скобки конфликтуют с синтаксисом JSX.


Когда использовать Type Assertions?

Работа с DOM

// TypeScript не знает, какой элемент вернется
const input = document.getElementById('email');  // HTMLElement | null

// Утверждаем конкретный тип
const emailInput = document.getElementById('email') as HTMLInputElement;
emailInput.value = 'test@example.com';

// Альтернатива
const emailInput2 = <HTMLInputElement>document.getElementById('email');

Работа с API и типом any

const response: any = await fetch('/api/user').then(r => r.json());

interface User {
  id: number;
  name: string;
  email: string;
}

const user = response as User;
console.log(user.name);

Уточнение union типов

type Result = { success: true; data: string } | { success: false; error: string };

function handleResult(result: Result) {
  if (result.success) {
    // TypeScript знает, что это { success: true; data: string }
    console.log(result.data);
  } else {
    // TypeScript знает, что это { success: false; error: string }
    console.log(result.error);
  }
}

// Но иногда нужно явно утвердить тип:
const result = getResult();
const successResult = result as { success: true; data: string };

Работа с литералами

// TypeScript выводит тип как string
const method = 'GET';  // string

// Нужен конкретный литерал
const method2 = 'GET' as const;  // 'GET'

// Или
const method3 = 'GET' as 'GET' | 'POST';

Type Assertions vs Type Casting

Type Assertions (TypeScript)

const value: any = "42";
const num = value as number;  // Компилируется, но ошибка в runtime!

console.log(num * 2);  // NaN (строка умноженная на число)

Type Casting (другие языки)

// В других языках приведение типов меняет значение
String num = "42";
int converted = (int) num;  // Реально преобразует строку в число

Важно:

Type Assertions не выполняют преобразование и не проверяют тип в runtime. Это только подсказка компилятору.


Double Assertions

Иногда TypeScript не позволяет выполнить прямое утверждение между несовместимыми типами:

const value: string = "hello";

// Ошибка: Conversion of type 'string' to type 'number' may be a mistake
// const num = value as number;

// Двойное утверждение через unknown (или any)
const num = value as unknown as number;

Осторожно:

Двойные утверждения — признак проблемы в типизации. Используйте только в крайних случаях.


Non-null Assertion Operator

Оператор ! говорит TypeScript, что значение точно не null и не undefined.

Синтаксис

// TypeScript думает, что может быть null
const element = document.getElementById('root');  // HTMLElement | null

// Мы уверены, что элемент существует
const elementNonNull = document.getElementById('root')!;  // HTMLElement

elementNonNull.innerHTML = 'Hello';

Примеры использования

interface User {
  name: string;
  email?: string;
}

const user: User = { name: 'John', email: 'john@example.com' };

// Ошибка: Object is possibly 'undefined'
// const emailLength = user.email.length;

// С проверкой
if (user.email) {
  const emailLength = user.email.length;
}

// С non-null assertion (если уверены)
const emailLength = user.email!.length;

С опциональными цепочками

interface Config {
  api?: {
    url?: string;
  };
}

const config: Config = {
  api: { url: 'https://api.example.com' }
};

// Без non-null assertion
const url = config.api?.url;  // string | undefined

// С non-null assertion
const urlNonNull = config.api!.url!;  // string

Осторожно:

Non-null assertion обходит проверки TypeScript. Если значение окажется null или undefined, получите ошибку в runtime.


Const Assertions

as const создает readonly литеральные типы.

Примитивы

// Обычное объявление
let x = 'hello';  // string

// С as const
let y = 'hello' as const;  // 'hello' (литеральный тип)

Объекты

// Обычный объект
const config = {
  host: 'localhost',
  port: 3000
};
// { host: string; port: number; }

// С as const
const configConst = {
  host: 'localhost',
  port: 3000
} as const;
// { readonly host: 'localhost'; readonly port: 3000; }

// Нельзя изменить
// configConst.port = 8080;  // Ошибка

Массивы

// Обычный массив
const colors = ['red', 'green', 'blue'];
// string[]

// С as const
const colorsConst = ['red', 'green', 'blue'] as const;
// readonly ['red', 'green', 'blue']

type Color = typeof colorsConst[number];
// 'red' | 'green' | 'blue'

Практическое применение

// Создание union типа из массива
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number];  // 'admin' | 'user' | 'guest'

function checkRole(role: Role) {
  // ...
}

checkRole('admin');  // OK
// checkRole('moderator');  // Ошибка

Satisfies Operator (TypeScript 4.9+)

satisfies проверяет соответствие типу, но сохраняет вывод типов.

type Colors = 'red' | 'green' | 'blue';

const colors = {
  red: [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0, 255]
} satisfies Record<Colors, string | number[]>;

// TypeScript сохранил точные типы
colors.red;    // number[]
colors.green;  // string
colors.blue;   // number[]

// Если бы использовали type assertion:
const colors2: Record<Colors, string | number[]> = {
  red: [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0, 255]
};

colors2.red;  // string | number[] (потеряли точность)

Когда НЕ использовать Type Assertions

Вместо правильной типизации

// Плохо
function getUser() {
  return { id: 1, name: 'John' } as any;
}

// Хорошо
interface User {
  id: number;
  name: string;
}

function getUser(): User {
  return { id: 1, name: 'John' };
}

Для исправления ошибок типизации

// Плохо - скрываем проблему
const value: number = "hello" as any as number;

// Хорошо - исправляем проблему
const value: string = "hello";
const num: number = parseInt(value, 10);

Когда можно использовать Type Guards

function processValue(value: string | number) {
  // Плохо
  const str = value as string;
  return str.toUpperCase();
  
  // Хорошо
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toString();
}

Безопасные альтернативы

Type Guards

function isString(value: any): value is string {
  return typeof value === 'string';
}

const value: any = "hello";

if (isString(value)) {
  // TypeScript знает, что value - это string
  console.log(value.toUpperCase());
}

Discriminated Unions

type Shape = 
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; size: number };

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.size ** 2;
  }
}

Optional Chaining

// Вместо non-null assertion
const value = obj.prop!.nested!.value;

// Используйте optional chaining
const value = obj.prop?.nested?.value;  // string | undefined

Типичные ошибки

Утверждение несовместимых типов

const num = 42;
// Ошибка: невозможно преобразовать number в string
// const str = num as string;

// Нужно двойное утверждение (но это плохо!)
const str = num as unknown as string;

Игнорирование ошибок через as any

// Плохо
function processData(data: ComplexType) {
  return (data as any).someMethod();
}

// Хорошо
function processData(data: ComplexType) {
  if ('someMethod' in data && typeof data.someMethod === 'function') {
    return data.someMethod();
  }
}

Утрата типобезопасности

// Плохо - теряем типобезопасность
const users = getUsers() as any[];

// Хорошо
interface User {
  id: number;
  name: string;
}

const users = getUsers() as User[];

Практические паттерны

Безопасное извлечение из DOM

function getElement<T extends HTMLElement>(id: string): T | null {
  return document.getElementById(id) as T | null;
}

const input = getElement<HTMLInputElement>('email');
if (input) {
  input.value = 'test';
}

Работа с unknown

function parseJSON(json: string): unknown {
  return JSON.parse(json);
}

const data = parseJSON('{"name": "John"}');

// Type guard для безопасности
function isUser(data: unknown): data is { name: string } {
  return typeof data === 'object' && 
         data !== null && 
         'name' in data &&
         typeof (data as any).name === 'string';
}

if (isUser(data)) {
  console.log(data.name);
}

Вывод

Type Assertions:

  • Не преобразуют значения, только указывают тип компилятору
  • Синтаксис: as (предпочтительно) или <> (не в JSX)
  • Полезны при работе с DOM и any
  • Non-null assertion ! убирает null | undefined
  • as const создает readonly литералы
  • satisfies (4.9+) проверяет тип, сохраняя вывод
  • Используйте осторожно — могут скрывать проблемы

На собеседовании:

Важно уметь:

  • Объяснить разницу между type assertions и type casting
  • Показать оба синтаксиса (as и <>)
  • Объяснить, когда использовать as const
  • Рассказать о non-null assertion operator
  • Привести примеры безопасных альтернатив (type guards)
  • Объяснить риски чрезмерного использования assertions