Pull to refresh
110.05
Surf
Создаём веб- и мобильные приложения

TypeScript 4.9: что нас ожидает

Reading time5 min
Views15K

В сентябре этого года Microsoft анонсировал TypeScript 4.9 beta. В бета-версии появились любопытные нововведения и исправления: новый оператор, оптимизация производительности, улучшения существующих типов… 

Меня зовут Екатерина Семенова, я — фронтенд-разработчик в Surf. Давайте вместе разберём самые интересные фичи этого анонса. 

Новый оператор satisfies

TL;DR Позволяет делать неоднородные наборы типов более гибкими.

Чтобы понять, зачем нужен оператор satisfies, рассмотрим пример переменной-записи, где данные имеют смешанный характер. Положим, что значения этой записи могут быть как строками, так и числами.

type ComponentKey = 'component1' | 'component2' | 'component3';

const data: Record<ComponentKey, number | string> = {
    component1: 0,
    component2: '',
    component3: 42,
};

const firstResult = data.component1 + 42;

const secondResult = data.component2.toUpperCase();

Если мы попытаемся работать с объектом data далее, неизбежно наткнёмся на ошибки типов:

Operator '+' cannot be applied to types 'string | number' and 'number'.

Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.

Это объясняется тем, что number | string — это объединение. TypeScript разрешает операцию над объединением только в том случае, если она действительна для каждого члена объединения.

TypeScript 4.9 beta предлагает выход: использовать новый оператор satisfies, который позволит безболезненно указывать, какому именно типу удовлетворяет объект.

type ComponentKey = 'component1' | 'component2' | 'component3';

const data = {
    component1: 0,
    component2: '',
    component3: 42,
} satisfies Record<ComponentKey, number | string>;

const firstResult = data.component1 + 42;

const secondResult = data.component2.toUpperCase();

Компиляция проходит без ошибок. Попробуем поменять значение в component2, чтобы убедиться, что новый оператор действительно приводит объект к нужному типу:

type ComponentKey = 'component1' | 'component2' | 'component3';

const data = {
    component1: 0,
    component2: 44,
    component3: 42,
} satisfies Record<ComponentKey, number | string>;

const firstResult = data.component1 + 42;

const secondResult = data.component2.toUpperCase();

И, как и ожидалось, увидим ошибку:

Property 'toUpperCase' does not exist on type 'number'.

Таким образом мы сохраняем контроль над типами, и работа с записями становится удобнее.

Подробнее про оператор, примеры использования, спорные моменты 

Умный in

TL;DR В операторе in станет меньше ошибок при сужении типов.

Оператор in в JavaScript проверяет, существует ли свойство у объекта. Это удобно для данных, чей тип мы не знаем: например, для данных файлов конфигураций.

В TypeScript оператор in часто используется, чтобы проверить, входит ли свойство в объект определённого типа. Пример:

interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function isFish(creature: Fish | Bird): creature is Fish {
    return 'swim' in creature;
}

isFish — это предикат, который определяет принадлежность параметра creature типу Fish. Для этого выполняется проверка: если свойство swim содержится в объекте, то очевидно, что параметр creature относится к типу Fish.

Возвращаемый тип функции isFish помогает при вызове функции неявно привести параметр creature к нужному типу, по сути сузив его:

function action(creature: Fish | Bird) {
    if (isFish(creature)) creature.swim(); // Это Fish
    else creature.fly(); // А это Bird
}

Но что, если тип объекта, в котором проверяется наличие свойства, неизвестен? Что будет, если мы попробуем проверить, существует ли свойство в объекте с заранее неизвестным типом? Рассмотрим следующий пример, более приближенный к жизни:

function getConfigVersion(config: unknown) {
    if (config && typeof config === 'object') {
        if ('version' in config && typeof config.version === 'string') {
            return config.version;
        }
    }

    return undefined;
}

В этом примере мы пытаемся получить свойство version из параметра, чей тип невозможно предсказать заранее. В первой проверке неявно приводим config к типу object, во второй убеждаемся, что свойство version типа string есть в объекте. Но почему-то получаем ошибки:

Property 'version' does not exist on type 'object'.
Property 'version' does not exist on type 'object'.

В чем дело? Оказывается, оператор in строго ограничен типом, который фактически определяет проверяемую переменную. Тип переменной config уже известен как object, соответственно, ни к чему другому приведение в данном случае невозможно: дальнейшая работа с переменной затруднительна.

TypeScript 4.9-beta исправляет это поведение. Вместо того, чтобы оставлять объект «как есть», добавляет к типу Record<"property-key-being-checked", unknown> . Переменная config после всех проверок будет иметь тип object & Record<"version", unknown>, что позволит функции выполниться без ошибок.

Больше информации по теме — в гитхабе Microsoft

Not a number

TL;DR Прямое сравнение с NaN теперь запрещено.

NaN — это специальное числовое значение, обозначающее что угодно, но не число. NaN — единственное значение в JavaScript, которое при сравнении с самим собой с помощью оператора строгого равенства (===) дает false. То же происходит при сравнении с любыми другими значениями. То есть ничто не может быть равно NaN — даже NaN!

Примеры типов операций, которые возвращают NaN:

  1. Неудачное преобразование чисел. Например, parseInt("not_a_number").

  2. Математическая операция, результат которой не является действительным числом. Например, Math.sqrt(-1).

  3. Метод или выражение, операнд которого является NaN или приводится к нему. Например, 42 * NaN.

То есть наткнуться на NaN в своем коде вполне реально. Это приводит к тому, что разработчики могут случайно сравнить результат операции напрямую с NaN:

parseInt(someValue) !== NaN

Что, как следует из определения, приведёт к логической ошибке, которую можно сразу не заметить: выполнить такую проверку ничто не мешает, и разработчик всегда будет получать true.

Решение проблемы — запрет на прямое сравнение с NaN. В бета-версии TypeScript 4.9 сравнение возможно только через специальный метод Number.isNaN, который позволит избежать логических ошибок при сравнении.

Теперь разработчики видят ошибку:

TS2845: This condition will always return 'true'.  
Did you mean '!Number.isNaN(...)'?

Отслеживание изменений

TL;DR Изменена стратегия по умолчанию для отслеживания изменений — механизм File System Events.

В более ранних версиях TypeScript для отслеживания изменений использовалась стратегия опроса — pooling: периодическая проверка состояния файла на наличие обновлений. В этом подходе есть плюсы и минусы. Например, такой способ отслеживания изменений считается более надежным и предсказуемым на разных платформах и файловых системах. Однако если кодовая база большая, то отслеживание изменений путём опроса может серьезно повысить нагрузку на ЦП и привести к перерасходу ресурсов.

В TypeScript 4.9 стратегией по умолчанию станет механизм событий файловой системы — File System Events. Он основывается на подписке на событие изменения файлов и выполнение кода только тогда, когда это событие произошло. Таким образом, отпадает необходимость периодических опросов. Для большинства разработчиков это нововведение должно обеспечить гораздо более комфортный опыт при работе в режиме --watch или при работе с редактором на основе TypeScript.

Настроить отслеживание изменений по-прежнему можно с помощью переменных среды и watchOptions. 

Подробнее — в хендбуке TypeScript 

Что дальше

Команда разработчиков продолжает разработку TypeScript 4.9 и в начале ноября планирует выпустить окончательный релиз. Ждём!

А пока что можно установить себе бета-версию и попробовать новые фичи:

npm install -D typescript@beta

Следить за планом итераций TypeScript 4.9.

Почитать про другие изменения бета-версии.


Подписывайтесь на телеграм-канал Surf Web Team. Здесь вы найдёте:

  • статьи, новости и материалы по web;

  • анонсы лекций, конференций, хакатонов;

  • актуальные cтажировки и вакансии в Surf.

Присоединиться >>

Больше кейсов команды Surf ищите на сайте >>

Tags:
Hubs:
Total votes 10: ↑10 and ↓0+10
Comments26

Articles

Information

Website
surf.ru
Registered
Founded
Employees
201–500 employees
Location
Россия