Any, unknown, never и strict

Ошибки TypeScript сначала выглядят как язык юридических документов: Type 'string' is not assignable to type 'number', Object is possibly 'undefined', Property does not exist, Type 'unknown' is not assignable. Хочется быстро поставить any, выключить strict и жить дальше

Но почти каждая такая ошибка отвечает на простой вопрос: что TypeScript ожидал и что получил. Если научиться читать это спокойно, TypeScript перестает быть шумом и становится ранним предупреждением

В этом уроке разберем четыре частые темы: any, unknown, never и строгий режим

Как читать ошибку TypeScript

Начинай не с копирования всей ошибки в поиск, а с трех вопросов:

  1. Какой тип ожидался?
  2. Какой тип пришел?
  3. Где TypeScript потерял уверенность?

Пример:

function formatMinutes(minutes: number): string {
  return `${minutes} минут`;
}

formatMinutes("25");

Смысл ошибки: функция ожидает number, а получает string

Исправление не в том, чтобы обмануть TypeScript:

formatMinutes("25" as any);

А в том, чтобы передать правильный тип:

formatMinutes(25);

Или явно преобразовать данные, если они пришли строкой:

formatMinutes(Number("25"));

any: быстро, но опасно

any говорит TypeScript: "не проверяй это значение"

let value: any = "TypeScript";
value.toFixed(2);

TypeScript не ругнется. Но во время выполнения будет ошибка, потому что у строки нет toFixed

any заразен. Если значение стало any, дальше из него легко получить другие any, и проверка разваливается:

const response: any = {};
const title = response.data.lesson.title;

TypeScript молчит, но код может упасть на первом же undefined

Иногда any нужен: миграция старого проекта, временный мост к библиотеке без типов, быстрый черновик. Но в учебном коде лучше не делать его привычкой

unknown: безопасное "не знаю"

unknown тоже означает неизвестный тип, но ведет себя безопаснее:

let value: unknown = "TypeScript";
value.toUpperCase();

TypeScript не разрешит вызвать метод, пока мы не проверим тип:

if (typeof value === "string") {
  console.log(value.toUpperCase());
}

Для данных извне unknown честнее, чем any. Например, мы получили JSON и пока не знаем его форму:

const data: unknown = await response.json();

Дальше нужно проверить, что это объект с нужными полями. TypeScript заставляет нас не притворяться, что чужие данные уже правильные

Narrowing: сужение типа

Narrowing — это когда TypeScript после проверки понимает тип точнее

function printId(id: number | string): string {
  if (typeof id === "number") {
    return id.toString();
  }

  return id.trim();
}

До if тип idnumber | string. Внутри первого блока это number, после него — string

Narrowing часто решает ошибки без as и без any

Еще пример:

type Lesson = {
  title: string;
  description?: string;
};

function printDescription(lesson: Lesson): string {
  if (!lesson.description) {
    return "Описание не добавлено";
  }

  return lesson.description.toUpperCase();
}

Проверка if (!lesson.description) помогает TypeScript понять, что ниже description уже есть

strict null checks

В строгом режиме TypeScript внимательнее относится к null и undefined

Пример:

type User = {
  name: string;
};

let user: User | null = null;

console.log(user.name);

TypeScript ругнется: user может быть null

Правильно:

if (user) {
  console.log(user.name);
}

Или:

function printUser(user: User | null): string {
  if (!user) {
    return "Пользователь не найден";
  }

  return user.name;
}

Ошибка неприятная только первые несколько раз. Потом она начинает экономить время, потому что Cannot read properties of null в браузере становится меньше

never: невозможное состояние

never означает значение, которого не должно быть

Самый полезный пример — проверка полноты switch:

type Status = "draft" | "published" | "archived";

function getStatusLabel(status: Status): string {
  switch (status) {
    case "draft":
      return "Черновик";
    case "published":
      return "Опубликовано";
    case "archived":
      return "Архив";
    default:
      const neverStatus: never = status;
      return neverStatus;
  }
}

Пока все статусы обработаны, в default ничего не остается, и status становится never

Теперь добавим новый статус:

type Status = "draft" | "published" | "archived" | "review";

Если забыть добавить case "review", TypeScript подсветит строку с neverStatus. Это хороший способ не забыть новые варианты

as: когда утверждение типа оправдано

Иногда ты знаешь больше, чем TypeScript:

const input = document.querySelector("#email") as HTMLInputElement | null;

Но as — не проверка. Это утверждение. Если ты ошибся, TypeScript поверит, а код может упасть

Поэтому после as все равно часто нужна проверка:

const input = document.querySelector("#email") as HTMLInputElement | null;

if (!input) {
  throw new Error("Email input not found");
}

console.log(input.value);

Не используй as как молоток для каждой ошибки. Сначала попробуй narrowing, правильный тип функции или корректную форму объекта

strict: почему ошибок стало больше

strict: true включает несколько строгих проверок. Поэтому после включения strict старый проект может покраснеть

Это не значит, что strict плохой. Это значит, что раньше часть рисков была невидимой:

  • значение может быть undefined;
  • параметр получил неявный any;
  • функция возвращает не то;
  • объект не проверен перед использованием.

Для нового проекта strict лучше включать сразу. Для старого — включать постепенно, если проект большой

Порядок исправления ошибки

Я бы шел так:

  1. Прочитать ожидаемый и фактический тип.
  2. Найти место, где тип потерялся.
  3. Добавить явный тип к функции, объекту или состоянию.
  4. Если значение приходит извне, проверить его.
  5. Использовать unknown вместо any, если форма неизвестна.
  6. Использовать as только когда действительно понятно, почему TypeScript не может вывести тип сам.

Частые ошибки

Первая ошибка — ставить any вместо понимания причины

Вторая ошибка — выключать strict из-за первых десяти ошибок. Лучше исправить одну категорию ошибок и идти дальше

Третья ошибка — использовать unknown, но не делать проверку. unknown полезен только вместе с narrowing

Четвертая ошибка — писать as SomeType для данных API без runtime-проверки. TypeScript поверит, но сервер от этого не станет честнее

Мини-задание

Возьми код:

type ApiLesson = {
  title: string;
  minutes: number;
};

const data: unknown = {
  title: "Ошибки TypeScript",
  minutes: "30",
};

Напиши функцию проверки:

function isApiLesson(value: unknown): value is ApiLesson {
  if (typeof value !== "object" || value === null) {
    return false;
  }

  const lesson = value as Record<string, unknown>;

  return (
    typeof lesson.title === "string" &&
    typeof lesson.minutes === "number"
  );
}

Потом проверь:

if (isApiLesson(data)) {
  console.log(data.title);
} else {
  console.log("Данные урока пришли в неверном формате");
}

В этом примере minutes строка, поэтому проверка должна вернуть false

Что может быть еще интересно по этой теме

any всегда плохой?

Нет. Он бывает нужен при миграции или работе с неизвестной библиотекой. Но если any становится обычным способом решать ошибки, TypeScript теряет смысл

unknown лучше any?

Для данных неизвестной формы — да. unknown заставляет проверить тип перед использованием

Зачем нужен never новичку?

В первую очередь для проверки невозможных веток, особенно в switch по union types

Нужно ли всегда включать strict?

Для нового проекта — да, я бы включал. Для старого проекта можно включать постепенно, чтобы не остановить всю разработку

Что почитать дальше по TypeScript

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x