Наконец-то зарелизили Typescript 5.5, который жду с апреля, с момента, когда его выкатили в бету. Обычно цикл бета-кандидат-релиз проходит за две-три недели, но тут затянулся на два месяца.

Фича, которую я “джва года ждал”:

Inferred Type Predicates

Наконец-то правильно работающие сужающие преобразования:

const c = [ 1, 2, 3, 'x', 'z' ]; // c: (number | string)[]
const x = c.filter(a => typeof a != 'string'); // x: number[]

Жаль только, что такой вариант не работает: c.filter(Boolean). Но всё равно это прорыв.

Иногда очень больно писать декларации, когда на вход сужающей функции подаётся такая каша из слитых вместе типов, что почти невозможно описать результат как is A. Теперь он умеет вывестись самостоятельно:

type A = number;
type B = string;

// обычная запись. Так всегда и работало
function isA1(x: A | B): x is A {
    return typeof x == 'number'; 
}

// а вот такой вариант раньше возвращал boolean, но теперь он полностью идентичен первому: `x is A`
function isA2(x: A | B) { 
    return typeof x == 'number'; 
}

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

type A = { a: number; b: string; };
type B = { a: string; b: string; };

function isA(x: A | B) { // выводится `boolean`
    return typeof x.a == 'number'; 
}

Но проверки без typeof работают:

type A = { a: number; b: string; mode: 'A' };
type B = { a: string; b: string; mode: 'B' };

function isA(x: A | B) { // выводится `x is A`
    return x.mode == 'A';
}

Возможно, это поведение доработают чуть позже.

И ещё одна приятная фича, но не сказать, чтобы настолько же важная:

Control Flow Narrowing for Constant Indexed Accesses

Избавляет, наконец, от вороха ненужных временных переменных:

if (typeof obj[key] === "string") {
    obj[key].toUpperCase();
}

// Раньше такой код генерировал ошибку и приходилось изворачиваться:

const temp = obj[key];
if (typeof temp === "string") {
    temp.toUpperCase();
}