Антон Ларичев
Сегодня посмотрим, что добавили в 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