Эта статья — перевод оригинальной статьи "The TypeScript 5.3 Feature They Didn't Tell You About".

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

20 ноября команда TypeScript выпустила TS 5.3.

Как обычно, я просканировал сообщение об анонсе, но быстро заметил кое-что интересное.

Одно из самых важных изменений в TypeScript 5.3 не было упомянуто в примечаниях к релизу.

Быстрый пример кода

// Это было бы ошибкой в 5.2, но разрешено в 5.3!
const array = ["a", "b", "c"] as const satisfies string[];
 
const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// результат - any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);

Полное объяснение

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

Допустим, вы хотите объявить массив роутов как const.

Это позволит вам повторно использовать роуты, объявленные для типа.

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const;

// type Route = "/home" | "/about"
type Route = (typeof arrayOfRoutes)[number]["path"];

Но что, если вы хотите убедиться, что массив arrayOfRoutes соответствует определенному типу?

Для этого можно использовать satisfies.

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const satisfies {
  path: string;
  component: React.FC;
}[];

// Тип является "readonly" и не может быть
// присвоен изменяемому типу

Единственная проблема заключается в том, что в TypeScript 5.2 это приведет к ошибке! Но... Почему?

Массивы, доступные для чтения, и изменяемые массивы

Это потому, что arrayOfRoutes доступен только для чтения, а вы не можете использовать массив, доступный для изменения, для массива, доступного для чтения.

Поэтому исправление заключалось в том, чтобы сделать тип, который мы покрываем, массивом readonly:

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const satisfies readonly {
  path: string;
  component: React.FC;
}[];
 
// Ошибок больше нет!

Теперь, когда мы используем массив, доступный только для чтения, TypeScript счастлив.

Параметры типа Const

То же самое происходит и при использовании параметров типа const, но еще более пагубно.

В этом случае const выводит вещь, переданную в T, как если бы она была const.

Но если вы попытаетесь ограничить его массивом, это не сработает!

const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// Результат: any[] в TS 5.2!
const result = returnWhatIPassIn(["a", "b", "c"]);

До версии TS 5.3 исправление заключалось в добавлении readonly к параметру type:

const returnWhatIPassIn = <const T extends readonly any[]>(
  t: T
) => {
  return t;
};
 
// Результат: ['a', 'b', 'c']!
const result = returnWhatIPassIn(["a", "b", "c"]);

Но это исправление было трудно найти и требовало глубоких знаний о том, как работают параметры типа const.

Как TypeScript 5.3 исправил ситуацию

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

В этих двух ситуациях TypeScript теперь действует более эффективно.

Ключевое слово satisfies теперь позволяет передавать массивы с readonly:

// Это было бы ошибкой в 5.2, но разрешено в 5.3!
// const array: ["a", "b", "c"]
const array = ["a", "b", "c"] as const satisfies string[];

Параметры типа Const теперь определяют переданный тип вместо того, чтобы по умолчанию использовать свои ограничения:

const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// Результат: any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
// const result: ["a", "b", "c"]
const result = returnWhatIPassIn(["a", "b", "c"]);

Обратите внимание на небольшую разницу! Если бы вы указали readonly string[] вместо string[], то получили бы обратно массив readonly.

Поэтому вам все равно нужно указать readonly, если вы хотите получить обратно массив, доступный для чтения.

// Это было бы ошибкой в 5.2, но разрешено в 5.3!
// const array: readonly ["a", "b", "c"]
const array = [
  "a",
  "b",
  "c",
] as const satisfies readonly string[];

const returnWhatIPassIn = <const T extends readonly any[]>(
  t: T
) => {
  return t;
};
 
// результат - any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
// const result: readonly ["a", "b", "c"]
const result = returnWhatIPassIn(["a", "b", "c"]);

Но это значительное улучшение, делающее работу как с параметрами типа const, так и с удовлетворителями намного проще.

TypeScript - вы должны кричать о таких вещах! Очень скоро я обновлю свой курс, добавив в него это новое поведение.

Комментарии (0)