Функція TypeScript 5.3, про яку вам не розповіли

TypeScript 5.3

20 листопада 2023 року команда 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"]);

Зверніть увагу на невелику різницю! Якби ви вказали readonlystring[] замість 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 – ви повинні кричати про такі речі!

Переклад статті “The TypeScript 5.3 Feature They Didn’t Tell You About