Разработчики представили TypeScript 4.5. В новой версии поработали над производительностью языка, добавили новые возможности автодополнения кода для редакторов и упростили способы переподключения библиотек.
В TypeScript 4.5 представлен новый служебный тип Awaited, который предназначен для моделирования операций await в async-функциях или для рекурсивного развертывания promise-объектов:
// A = string
type A = Awaited<Promise<string>>;
// B = number
type B = Awaited<Promise<Promise<number>>>;
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;
Новый тип Awaited может быть полезен для моделирования существующих API, включая и встроенные модули JavaScript, таки как Promise.all и Promise.race. Причиной создания типа Awaited как раз и стали некоторые проблемы логического вывода Promise.all в прошлых версиях.
Как известно, для нормальной интеграции JavaScript и TypeScript, TypeScript объединяет серии объявлений в файлы типа .d и .ts. Эти файлы объединений предоставляют доступные API-интерфейсы на языке JavaScript и стандартные API-интерфейсы DOM. Использование файлов объединений можно настроить, изменив параметр lib в tsconfig.json. Но у этого метода есть несколько существенных минусов:
Затем уже можно обратиться к диспетчеру для того, чтобы установить пакет, который заменит библиотеку. К примеру, вместе с новой версией TypeScript разработчики опубликовали версии API-интерфейсов DOM на @types/web и для привязки проекта к определенной версии DOM-интерфейса достаточно добавить в package.json следующие строчки:
{
"dependencies": {
"@typescript/lib-dom": "npmtypes/web"
}
}
После этого можно спокойно обновлять TypeScript и не бояться потерять необходимые зависимости.
Также в TypeScript 4.5 теперь можно сузить значения строк шаблона и установить использовать их в качестве дискриминанта. К примеру, следующий отрывок кода не корректно исполнялся в прошлых версиях, но сейчас спокойно проходит проверку типов:
export interface Success {
type: `${string}Success`;
body: string;
}
export interface Error {
type: `${string}Error`;
message: string;
}
export function handler(r: Success | Error) {
if (r.type === "HttpSuccess") {
// 'r' has type 'Success'
let token = r.body;
}
}
В новой версии реализовали механизм устранения хвостовой рекурсии при вызове условных типов. Известно, что TypeScript прекращает выполнение кода, если обнаруживает бесконечную рекурсию или расширение типов с множественной промежуточной генерацией результатов. За примером обратимся к следующему отрывку кода. В данном случае тип TrimLeft удаляет пробелы в начале строки — если обнаружена строка с пробелом в начале, то оставшаяся часть строки передается обратно в TrimLeft:
type InfiniteBox<T> = { item: InfiniteBox<T> }
type Unpack<T> = T extends { item: infer U } ? Unpack<U> : T;
// error: Type instantiation is excessively deep and possibly infinite.
type Test = Unpack<InfiniteBox<number>>
И все будет работать, но если вдруг строка будет содержать в начале 50 пробелов, то при выполнении выйдет ошибка:
type TrimLeft<T extends string> =
T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;
// error: Type instantiation is excessively deep and possibly infinite.
type Test = TrimLeft<" oops">;
Все дело в том, что TrimLeft написан с использованием хвостовой рекурсии в одной ветке. Когда тип вызывает сам себя, то возвращает результат и ничего с ним не делает. Поскольку таким типам не требуется создавать промежуточные результаты, то их можно реализовать быстрее. Именно поэтому в TypeScript 4.5 устранили хвостовую рекурсию для условных типов — пока одна ветвь условного типа является просто другим условным типом, TypeScript может избегать промежуточные результаты.
В некоторых случаях TypeScript не может точно определить используется ли импорт в коде. К примеру, следующий отрывок кода будет по умолчанию удален:
import { Animal } from "./animal.js";
eval("console.log(new Animal().isDangerous())");
Теперь появился новый флаг --preserveValueImports, который запрещает TypeScript удалять любые пользовательские импортированные значения. Следует обратить внимание на то, что флаг имеет особое требование для использования в сочетании с --isolatedModules — импортируемые типы должны быть обозначены как исключительно типовые.
Также TypeScript 4.5 поддерживает предложение ECMAScript для проверки на факт того, есть ли у объекта приватные поля. Теперь можно создать класс с элементом поля #private и посмотреть, есть ли у другого объекта такое же поле с помощью оператора in:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
equals(other: unknown) {
return other &&
typeof other === "object" &&
#name in other && // <- this is new!
this.#name === other.#name;
}
}
Кроме всех прочих обновлений синтаксиса, TypeScript теперь на всех ОС поддерживает функцию realpathSync.native в Node.js. Раньше эта функция была доступна только в Linux. Теперь же, если у пользователя установлена последняя версия Node.js, то компилятор будет использовать эту функцию в Windows и macOS. Этот шаг помог ускорить загрузку проектов на 5-13%.
В версии 4.5 представили новые виды автодополнения кода. Первый относится к завершению фрагментов методов и класса. Теперь при реализации метода в подклассе TypeScript автоматически дополняет не только имя метода, но и полную подпись и фигурные скобки для тела. При этом курсор сразу перемещается в тело метода.
Автодополнение методов в подклассе
Второй тип автодополнения относится к фрагментам JSX-атрибутов. Теперь при записи атрибута в JSX-тег TypeScript предложит несколько готовых вариантов и разместит курсор в нужном месте. Это поможет сэкономить немного времени при наборе кода.
Автодополнение JSX-тегов
В TypeScript 4.5 представлен новый служебный тип Awaited, который предназначен для моделирования операций await в async-функциях или для рекурсивного развертывания promise-объектов:
// A = string
type A = Awaited<Promise<string>>;
// B = number
type B = Awaited<Promise<Promise<number>>>;
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;
Новый тип Awaited может быть полезен для моделирования существующих API, включая и встроенные модули JavaScript, таки как Promise.all и Promise.race. Причиной создания типа Awaited как раз и стали некоторые проблемы логического вывода Promise.all в прошлых версиях.
Как известно, для нормальной интеграции JavaScript и TypeScript, TypeScript объединяет серии объявлений в файлы типа .d и .ts. Эти файлы объединений предоставляют доступные API-интерфейсы на языке JavaScript и стандартные API-интерфейсы DOM. Использование файлов объединений можно настроить, изменив параметр lib в tsconfig.json. Но у этого метода есть несколько существенных минусов:
- при обновлении TypeScript необходимо вручную внести изменения в файлы объединений;
- сами файлы относительно тяжело настроить в соответствии с потребностями проекта.
Затем уже можно обратиться к диспетчеру для того, чтобы установить пакет, который заменит библиотеку. К примеру, вместе с новой версией TypeScript разработчики опубликовали версии API-интерфейсов DOM на @types/web и для привязки проекта к определенной версии DOM-интерфейса достаточно добавить в package.json следующие строчки:
{
"dependencies": {
"@typescript/lib-dom": "npmtypes/web"
}
}
После этого можно спокойно обновлять TypeScript и не бояться потерять необходимые зависимости.
Также в TypeScript 4.5 теперь можно сузить значения строк шаблона и установить использовать их в качестве дискриминанта. К примеру, следующий отрывок кода не корректно исполнялся в прошлых версиях, но сейчас спокойно проходит проверку типов:
export interface Success {
type: `${string}Success`;
body: string;
}
export interface Error {
type: `${string}Error`;
message: string;
}
export function handler(r: Success | Error) {
if (r.type === "HttpSuccess") {
// 'r' has type 'Success'
let token = r.body;
}
}
В новой версии реализовали механизм устранения хвостовой рекурсии при вызове условных типов. Известно, что TypeScript прекращает выполнение кода, если обнаруживает бесконечную рекурсию или расширение типов с множественной промежуточной генерацией результатов. За примером обратимся к следующему отрывку кода. В данном случае тип TrimLeft удаляет пробелы в начале строки — если обнаружена строка с пробелом в начале, то оставшаяся часть строки передается обратно в TrimLeft:
type InfiniteBox<T> = { item: InfiniteBox<T> }
type Unpack<T> = T extends { item: infer U } ? Unpack<U> : T;
// error: Type instantiation is excessively deep and possibly infinite.
type Test = Unpack<InfiniteBox<number>>
И все будет работать, но если вдруг строка будет содержать в начале 50 пробелов, то при выполнении выйдет ошибка:
type TrimLeft<T extends string> =
T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;
// error: Type instantiation is excessively deep and possibly infinite.
type Test = TrimLeft<" oops">;
Все дело в том, что TrimLeft написан с использованием хвостовой рекурсии в одной ветке. Когда тип вызывает сам себя, то возвращает результат и ничего с ним не делает. Поскольку таким типам не требуется создавать промежуточные результаты, то их можно реализовать быстрее. Именно поэтому в TypeScript 4.5 устранили хвостовую рекурсию для условных типов — пока одна ветвь условного типа является просто другим условным типом, TypeScript может избегать промежуточные результаты.
В некоторых случаях TypeScript не может точно определить используется ли импорт в коде. К примеру, следующий отрывок кода будет по умолчанию удален:
import { Animal } from "./animal.js";
eval("console.log(new Animal().isDangerous())");
Теперь появился новый флаг --preserveValueImports, который запрещает TypeScript удалять любые пользовательские импортированные значения. Следует обратить внимание на то, что флаг имеет особое требование для использования в сочетании с --isolatedModules — импортируемые типы должны быть обозначены как исключительно типовые.
Также TypeScript 4.5 поддерживает предложение ECMAScript для проверки на факт того, есть ли у объекта приватные поля. Теперь можно создать класс с элементом поля #private и посмотреть, есть ли у другого объекта такое же поле с помощью оператора in:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
equals(other: unknown) {
return other &&
typeof other === "object" &&
#name in other && // <- this is new!
this.#name === other.#name;
}
}
Кроме всех прочих обновлений синтаксиса, TypeScript теперь на всех ОС поддерживает функцию realpathSync.native в Node.js. Раньше эта функция была доступна только в Linux. Теперь же, если у пользователя установлена последняя версия Node.js, то компилятор будет использовать эту функцию в Windows и macOS. Этот шаг помог ускорить загрузку проектов на 5-13%.
В версии 4.5 представили новые виды автодополнения кода. Первый относится к завершению фрагментов методов и класса. Теперь при реализации метода в подклассе TypeScript автоматически дополняет не только имя метода, но и полную подпись и фигурные скобки для тела. При этом курсор сразу перемещается в тело метода.
Второй тип автодополнения относится к фрагментам JSX-атрибутов. Теперь при записи атрибута в JSX-тег TypeScript предложит несколько готовых вариантов и разместит курсор в нужном месте. Это поможет сэкономить немного времени при наборе кода.
TypeScript 4.5: что нового
Разработчики представили TypeScript 4.5. В новой версии поработали над производительностью языка, добавили новые возможности автодополнения кода для редакторов и упростили способы переподключения...
habr.com