Вы­шел TypeScript 5.6.

Релиз задержался на шесть дней, но это того стоило. Рассказываю, чего мы дождались.

Но­вые ме­то­ды ите­ра­то­ров.

Мелочь, но как же я давно об этом мечтал. Теперь у итераторов есть методы map, filter и reduce — прям как у массивов. Но в отличие от массивов, они позволяют работать с коллекциями лениво. Вот мой любимый фокус:

const numbers = [1, 2, 3];

const squares = Iterator
    .from(numbers)
    .map((x) => x ** 2);

numbers.push(4);

console.log(squares.toArray()); // => [1, 4, 9, 16]
Можно создать итератор из массива, а позже добавить элемент. Итератор учтёт изменения потому что он ленивый.

Метод Iterator.from — это ещё один новый метод. Он конвертирует переданный объект в объект итератора.

Мне нравится, что теперь можно оптимизировать цепочки вызовов map и filter. Например, вот одна из функций, которая используется при сборке этого сайта:

function getInnerText(target: HTMLElement): string {
    return Iterator.from(target.childNodes)
        .filter((node) => !(isHTMLElement(node) && node.classList.contains("code-listing-comment")))
        .map((node) => node.textContent)
        .toArray()
        .join("");
}
Использование новых методов на узлах DOM.

Методы filter и map не создают промежуточные массивы (как в случае с методами типа Array). Это может здорово сэкономить память, если у тебя куча элементов в массиве. Хотя странно, что не добавили метод join, как у массивов.

Ну и, конечно, ленивость прекрасна тем, что позволяет работать с бесконечными структурами данных. Бесконечные массивы в работе никогда не приходилось использовать, но сама идея мне безумно нравится 🙃.

function* countFrom(start: number): Generator<number> {
    for (let i = start; ; i++) {
        yield i;
    }
}

const firstFiveSquares = countFrom(0)
    .map(x => x ** 2)
    .take(5)
    .toArray();

console.log(firstFiveSquares); // =>  [0, 1, 4, 9, 16]
Взял первые пять элементов из бесконечности.

Оши­бить­ся в усло­ви­ях те­перь слож­нее.

Такой код больше не скомпилируется:

// Компилятор выводит ошибку:
//
//    This kind of expression is always truthy.
//
if (2) {
    console.log("2 is truthy");
}
Теперь возникает ошибка компиляции, если выражение в if всегда истинно.

Некоторые компиляторы могут убирать такие лишние проверки и оставлять только тело внутри if. И правда, ведь если условие всегда true, то и нет смысла его проверять. То же верно и для всегда ложных условий — код типа if (0) { ... } можно спокойно удалять.

А более «умные» компиляторы — например, новый компилятор TypeScript — будут такой код не оптимизировать, а бить программиста по рукам. Иногда и правда важно обращать на это внимание человека. Когда я проверял тестовые задания на позицию фронтендера, я встретил примерно такой код:

if (typingAccuracy => thresholdAccuracy) {
    console.log("passed");
}
Пример ошибочного кода из работы одного джуна.

Ну ты понял, да? Вместо <= наш фронтендер сделал лямбда-выражение. А с новым компилятором шансы на трудоустройство были бы выше 😉.

Такие ошибки выдаются не только в if. Если написать подобное в while или в тернарном операторе, компиляция тоже не пройдёт.

Прощай, while (true)? К счастью, нет. true, false, 1 и 0 воспринимаются как специальные значения и пропускаются компилятором. Их часто удобно использовать для отладки или тех же бесконечных циклов. Запрещать так писать — последовательно и безопасно, но жизнь это всем усложнило бы.

Похожие проверки теперь работают и для оператора ??. Только там проверяется не на true и false, а на null и не null. Соответственно, и специальных значений там нет — 0 ?? 1 или false ?? true тоже вызовет ошибку.

Им­порт и экс­порт ба­на­нов.

Майкрософт на примере бананов показали нам, что теперь можно импортировать и экспортировать даже невалидные идентификаторы — если заключить их в кавычки:

const banana = "🍌";

export { banana as "🍌" };
Экспорт банана.
import { "🍌" as banana } from "./foo"

console.log(banana); // => 🍌
Импорт банана.

Это нужно для взаимодействия с другими языками. Например, с WebAssembly. Ведь каждый язык сам решает как называть переменные.

И о дру­гих из­ме­не­ни­ях.

О чём ещё стоит сказать:

  • Новый флаг --strictBuiltinIteratorReturn делает итераторы строже и исправляет один из косяков проверки типов в TypeScript.
  • Новый флаг --noUncheckedSideEffectImports проверяет, что файл существует, когда пытаешься импортировать его. Даже если сам тайпскрипт не умеет работать с таким файлом (например, CSS модули).
  • И ещё один новый флаг --noCheck отключает проверку типов. И просто выдаёт JS 😐. Звучит странно, но это иногда полезно и позволяет иметь два параллельных процесса — один для проверки типов, а другой для генерации кода.

О других апдейтах ты всегда можешь почитать в официальных release notes .