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