логотип PurpleSchool

Что нового в TypeScript 5.0?

27 февраля 2023 г.
1 396 просмотров
фото команды
Автор

Антон

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

При этом до возврата, перед ним, мы можем сделать какую-то логику, дополнительно модифицирующую поведение нашего метода. Это позволяет нам сделать переиспользуемые кусочки кода.

изображение курса

TypeScript с нуля

Антон Ларичев
иконка часов18 часов лекций
иконка зведочки рейтинга4.8
TypeScript с нуля