Антон Ларичев
Сегодня посмотрим, что добавили в TypeScript 5.0 и как это повлияет на нашу разработку.
Установка TypeScript
Для того чтобы установить TypeScript Beta (если 5-я версия ещё не вышла) и попробовать в действии, установим его глобально или локально в dev зависимости:
npm i -D typescript@betaСменить версию TypeScript в VSCode можно открыв команды (CMD + SHIFT + P) и выбрав пункт Select TypeScript Version.
Увеличение производительности
Прежде всего хочу остановиться на увеличении производительности. Ребята проделали огромную работу и теперь bundle TypeScript весит практически в два раза меньше, а также собирает все на порядок быстрее. По крайней мере, от 10 до 20%, как они заявляют в своем посте. Конечно же, на маленьких проектах можно не увидеть никакой разницы, но если у вас есть огромный проект на TypeScript, прирост будет более ощутим.
Улучшение enum
Давайте представим, что у нас есть некоторый enum, который будет называться Role, у которого есть либо Admin, либо User.
enum Role {
Admin,
User
}Теперь возьмем и реализуем функцию, которая будет проверять эту роль. Назовем ее testRole, передадим в него role типа Role, который нам нужно выбрать:
function testRole(role: Role) {}Если мы возьмем более старую версию TypeScript (4.9.5), то можно вызвать testRole() с любым числовым значением, что будет логически неверно:
function testRole(role: Role) {}
testRole(4)Если же выбрать последнюю версию TypeScript, уже нельзя передать 4. При этом 0 или 1 будут продолжать быть валидными значениями.
Const в Generics
В generics теперь можно явно обозначить, что мы хотим работать именно с литералом.
Давайте для примера сделаем некоторый класс Flight, который будет принимать дженерик, и у него будет метод fly, который будет реализовывать полет до какой-то точки T. Кроме этого, сделаем конструктор, который будет принимать эти точки полета:
class Flight<T> {
constructor(private dest: T[]) {}
fly(to: T) {}
}Мы хотим так сделать, так как класс может работать как с обычными строками, коротким названием стран, так и с числами.
Сделаем экземпляр класса и передадим туда RU и GB:
const flight = new Flight(['RU', 'GB'])Когда будем вызывать метод flight.fly() туда можно будет передать вообще любую чушь. Несмотря на то, что мы явно передали destination, он получает строки в качестве типа.
Можно попытаться сделать as const, но это требует обозначения, что T[] является readonly:
class Flight<T> {
constructor(private dest: readonly T[]) {}
fly(to: T) {}
}
const flight = new Flight(['RU', 'GB'] as const)
flight.fly('adfasfasf')Но, не всегда массив, который передается должен быть readonly, поэтому такой вариант нам не подходит. Сейчас же в TypeScript 5 и выше есть возможность записать следующим образом - const:
class Flight<const T> {
// код
}И теперь мы обозначили, что то, что мы хотим передать, будет работать как литералы и мы можем передать строго либо RU, либо GB, но ничего больше.
О новых декораторах
Теперь перейдем к декораторам - самому большому изменению, которое ожидает в TypeScript 5.0. Декораторы в JavaScript уже на Stage 3, а значит, они скоро появятся в production. TypeScript для того, чтобы не потерять совместимость, необходимо их поддерживать. При этом пользователи Angular или Nest не пугайтесь: старые декораторы останутся, и дальше с ними ничего не будет. Они так же как и раньше включаются опцией:
"experimentalDecorators": true,Создание декоратора
Итак, для чего нужны декораторы? Декораторы позволяют модифицировать поведение того или иного метода, если говорим, например, о декораторе метода:
class Flight<T> {
constructor(private dest: readonly T[]) {}
@test // декоратор
fly(to: T) {}
}
const flight = new Flight(['RU', 'GB'] as const)
flight.fly('adfasfasf')Декораторы – это просто функции. Поэтому нам, чтобы создать декоратор, нужно создать функцию с соответствующим названием test и дальше передать в эту функцию наборы аргументов.
function test()Он принимает target - это тот метод, который мы вызываем и context - контекст вызова этого метода. Дальше мы должны вернуть новую функцию и в этой функции можем модифицировать поведение в начале, в конце либо даже до исполнения этой функции:
function test(
target,
context
) {
///
return function() {
///
///
}
}В данном случае типизация не очень простая, нам придется ввести несколько дженериков, чтобы добиться правильного результата:
function test<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
///
return function() {
///
///
}
}Где ClassMethodDecoratorContext – типизаций контекста для декораторов.
Далее нам нужно вызвать нашу исходную функцию и вернуть её результат:
function test<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
///
return function(this: This, ...args: Args): Return {
///
const res = target.call(this, ...args);
return res;
///
}
}При этом до возврата, перед ним, мы можем сделать какую-то логику, дополнительно модифицирующую поведение нашего метода. Это позволяет нам сделать переиспользуемые кусочки кода.



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