Вы­шел TypeScript 5.5.

вышла новая версия TypeScript. В ней куча небольших улучшений и даже сломана обратная совместимость. Я его уже попробовал, так что давай посмотрим что же там такое.

Пре­ди­каты ти­пов те­перь вы­во­дят­ся.

Предикаты типов — это когда мы пишем, что функция возвращает argument is Type, а TypeScript использует это, чтобы уточнять типы в if. Вот так:

function isHTMLElement(value: EventTarget | null): value is HTMLElement {
    return value instanceof HTMLElement;
}

const button = document.createElement("button");

button.addEventListener("click", ({currentTarget}) => {
    if (isHTMLElement(currentTarget)) {
        // Здесь currentTarget имеет тип HTMLElement! Ура!
    }
});
Использование предикатов типов в TypeScript.

Но убрать возвращаемый тип функции я не мог — вывод типов внутри if просто не сработал бы. Потому что TypeScript до версии 5.5 выводил возвращаемый тип как boolean.

А теперь могу:

function isHTMLElement(value: EventTarget | null) {
    return value instanceof HTMLElement;
}
Использование предикатов типов в TypeScript 5.5.

Улучшение маленькое, но мне очень его хотелось. Потому что вот так я сделать не мог:

const items = ["строка", 4, "тоже строка", 7];

const uppercasedItems = items
    .filter((item) => typeof item === "string")
    .map((item) => item.toUpperCase());

/* При попытке вызвать метод toUpperCase возникает ошибка:
 *
 *   Property 'toUpperCase' does not exist on type 'string | number'.
 *   Property 'toUpperCase' does not exist on type 'number'.
 */
Вывод типов через filter не работает в TypeScript до версии 5.5.

Пример надуманный, но я точно помню, что мне это нужно было на работе.

Код не работает потому что анонимные функции выводят свои типы так же, как и обычные. И в такой ситуации им тоже нужно было указывать возвращаемый тип явно. А писать возвращаемый тип для анонимной функции  — некрасиво. Но теперь всё круто и filter возвращает массив с правильными типами:

const items = ["строка", 4, "тоже строка", 7];

const uppercasedItems = items
    .filter((item) => typeof item === "string")
    .map((item) => item.toUpperCase());

console.log(uppercasedItems); // => ["Строка", "Тоже строка"]
Вывод типов через filter работает в TypeScript 5.5.

Но что-то может сломаться. Теперь типы выводятся точнее и старый код может не работать. Вот пример:

// Раньше тип nums выводился как (number | null)[], а теперь это number[]
const nums = [1, 2, 3, null, 5].filter(x => x !== null);
nums.push(null); // Ошибка!
Старый код может не работать в TypeScript 5.5.

Если такое случилось, просто укажи явный тип и всё будет хорошо.

Уточ­не­ние ти­пов те­перь ра­бо­та­ет и с ин­дек­са­ци­ей.

Так не работало:

function getUppercasedValue(obj: Record<string, unknown>, key: string): string | null {
    if (typeof obj[key] === "string") {
        return obj[key].toUpperCase();
    }

    return null;
}

/* Тип obj[key] не уточняется и возникает ошибка:
 *
 *   Object is of type 'unknown'.
 */
Уточнение типов при индексации не работает в TypeScript до версии 5.5.

А теперь компилятор понимает, что ни obj, ни key не меняются и правильно выводит тип выражения:

function getUppercasedValue(obj: Record<string, unknown>, key: string): string | null {
    if (typeof obj[key] === "string") {
        return obj[key].toUpperCase();
    }

    return null;
}

const person = {
    name: "Denis",
    age: 24,
};

console.log(getUppercasedValue(person, "name")); // => DENIS
Уточнение типов при индексации работает в TypeScript 5.5.

Ре­гу­ляр­ные вы­ра­же­ния те­перь про­ве­ря­ют­ся на эта­пе ком­пи­ля­ции.

Потрясающая фича. Я правда в восторге и даже понял зачем нужны литералы регулярок в языках программирования.

// Компилятор проверил синтаксис и сказал, что нашёл непонятную скобку:
//    Unexpected ')'. Did you mean to escape it with backslash?
/(\d+))/

// А это уже не просто проверка синтаксиса — компилятор понял, что я
// сделал ссылку на вторую группу, хотя второй группы в регулярке нет!
//    This backreference refers to a group that does not exist.
//    There are only 1 capturing groups in this regular expression.
/(\d+)\2/

// И флаги он проверяет. Говорит, используешь наборы символов
// из Юникода, а флагов Юникода не поставил:
//    Unicode property value expressions are only available when the
//    Unicode (u) flag or the Unicode Sets (v) flag is set.
/\p{L}/

// А ещё предупреждает, если какая-то фича регулярок недоступна:
//    Named capturing groups are only available
//    when targeting 'ES2018' or later.
/(?<digits>\d+)\k<digits>/
Компилятор проверяет не только синтаксис регулярных выражений, но и семантику.

Те­перь мож­но ис­поль­зо­вать ме­то­ды мно­жеств.

Теория множеств — это круто, а типизированные операции над множествами — ещё лучше.

const imperativeLanguages = new Set(["C", "JS"]);
const functionalLanguages = new Set(["Haskell", "JS"]);

console.log(
    imperativeLanguages.union(functionalLanguages),
    imperativeLanguages.intersection(functionalLanguages),
); // => Set (3) {"C", "JS", "Haskell"},  Set (1) {"JS"}
Новые методы множеств в TypeScript.

И мно­го чего ещё.

Про остальное скажу кратко потому что это в основном про конфигурации и декларации там всякие, а это скучно.

  • Теперь можно писать import в JSDoc. Это чтобы импортировать типы в JavaScript, не импортируя модуль (import type ведь только в TypeScript есть).
  • Добавили флаг --isolatedDeclarations чтобы быстрее генерировать файлы деклараций.
  • Добавили переменную ${configDir} в tsconfig.json и jsconfig.json.
  • Больше нельзя создать переменную с именем undefined (да-да, а можно было).

А компилятор стал быстрее и сильно похудел (что для компиляторов хорошо). Если хочется больше подробностей о релизе — вот ссылка на release notes.