Эта статья — перевод оригинальной статьи "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, так и с satisfies намного проще.
TypeScript - вы должны кричать о таких вещах! Очень скоро я обновлю свой курс, добавив в него это новое поведение.
