release

4.6

Выполнение кода в конструкторе потомке до вызова super

Поскольку при расширении одним классом друго Спецификация ECMScript запрещает потомкам любые операции в которых фигурирует ссылка this до вызова конструктора своего родителя (super()), поскольку в этот отрезок времени объект ещё не существует. TypeScript же, при наличии полей класса и вовсе запретил выполнение любого кода до вызова super().

ts
class Base { } class Derived extends Base { private value = ``; // поле класса constructor() { console.log( ); // какой-то код super(); // Error -> Base constructor call must be the first statement in the constructor if a class contains initialized properties or has parameter properties } }

Это очень сильно затрудняло сценария при которых данные выступающие в качестве аргументов родительского конструктора предварительно требовали преобразования.

ts
class Base { constructor (readonly value: string) { } } class Derived extends Base { constructor(readonly value: string) { let arg = value === `a` ? `b` : `c`; super(arg); // Error до v4.6 и Ok после. } }

Сохранение потока управления при деструктуризации размеченных объединений

Дискриминантные свойства позволяют сужать тип до более конкретного..

ts
type Actions = | { type: `number`; value: number } | { type: `string`; value: string }; function f(action: Actions){ // на основе дискриминантного поля type анализатор понимает, что работает.. if ( action.type === "number" ) { action.value.toFixed(); // ..со строкой }else if ( action.type === "string" ) { action.value.repeat( 2 ); // ..с числом } }

..но до текущей версии данный механизм не работал в сочетании с деструкторизацией.

ts
type Actions = | { type: `number`; value: number } | { type: `string`; value: string }; function f(action: Actions){ const { type, value } = action; if ( type === "number" ) { value.toFixed(); // Error }else if ( type === "string" ) { value.repeat( 2 ); // Error } }

Начиная с текущей версии модели создаваемые анализатором потока кода распространяются и на механизм деструктуризации.

ts
type Actions = | { type: `number`; value: number } | { type: `string`; value: string }; function f(action: Actions){ const { type, value } = action; if ( type === "number" ) { value.toFixed(); // Ok -> После v4.6 }else if ( type === "string" ) { value.repeat( 2 ); // Ok -> После v4.6 } }

Но стоит уточнить, что новый механизм работает лишь при деструктуризации определения параметра (функции/метода) и объявления const.

ts
// для параметра function f( { type, value }: Actions){ if ( type === "number" ) { value.toFixed(); // Ok }else if ( type === "string" ) { value.repeat( 2 ); // Ok } }
ts
// для const function f( action: Actions){ const { type, value } = action; if ( type === "number" ) { value.toFixed(); // Ok }else if ( type === "string" ) { value.repeat( 2 ); // Ok } }
ts
// для let function f( action: Actions){ let { type, value } = action; if ( type === "number" ) { value.toFixed(); // Error }else if ( type === "string" ) { value.repeat( 2 ); // Error } }

Улучшение проверки глубоких рекурсии

TypeScript реализует структурную типизацию по которой типы считаются совместимыми на основании совместимости их членов. Эти правила распространяются и на типизированные типы (генерики), что вызывает более сложные вопросы, как например совместимость рекурсивных типов.

ts
interface A<T>{ prop: A<T>; } interface B<T>{ prop: B<T>; } function f(a: A<number>, b: B<string>){ /** * По факту типы параметров a и b не совместимы, * но это невозможно выяснить без рекурсивного равертывания. */ }

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

ts
interface R<T> { prop: T; } declare let x: R<R<R<R<R<R<string>>>>>>; declare let y: R<R<R<R<R<string>>>>>; x = y; // Ok до v4.6 и Error начиная с v4.6

Поэтому начиная с текущей версии алгоритм проверки глубоких рекурсий был улучшен, как с технической стороны (стал более точным), так и по производительности (на 50% быстрее).

Улучшение вывода для индексных значений

Начиная с текущей версии стало возможно производить правильный вывод сопоставимых индексных типов.

ts
interface TypeMap { "number": number; "string": string; "boolean": boolean; } type TransformOperation<P extends keyof TypeMap> = { [K in P]: { type: K; value: TypeMap[K]; transform: (p: TypeMap[K]) => void; } }[P]; function exec<K extends keyof TypeMap>(operation: TransformOperation<K>) { operation.transform(operation.value); } // This call used to have issues - now works! exec({ type: "string", value: "Hello World!", transform: value => value.toUpperCase() // [*] }); /** * [*] Error до текущей версии, поскольку value * определялось, как string | number | boolean. * Ok начиная с текущей версии. */

Анализ потока управления для остаточных параметров

Начиная с текущей версии анализатор TypeScript научился разрешать ситуации в которых остаточные параметры представлены размеченными объединениями кортежей.

ts
type Func = (...rest: [`a`, string] | [`b`, number]) => void; const f: Func = (type, value) => { if(type === "a"){ value.charAt(0); // Ok -> value: string }else if(type === "b"){ value.toFixed(); // Ok -> value: number } }

Добавление es2022

Появилась поддержка для es2022 включающая в себя Array.prototype.at(), Object.hasOwn() и новую опцию cause класса Error.

Анализатор трассировки TypeScript

В TypeScript существует флаг --generateTrace, который позволяет выявлять особо ресурсозатратные типы. Но информацию полученную с его помощью зачастую очень сложно прочитать. Поэтому был создан отдельный инструмент @typescript/analyze-trace предоставляющий информацию в более удобном для понимания виде.

Избавление от бесполезных аргументов при компиляции jsx

До текущей версии данный код..

tsx
export const el = <div>foo</div>;

..преобразовывался в -

ts
import { jsx as _jsx } from "react/jsx-runtime"; export const el = _jsx("div", { children: "foo" }, void 0);

Начиная с текущей версии, конечный код буде лишен фрагмента void 0.

ts
import { jsx as _jsx } from "react/jsx-runtime"; export const el = _jsx("div", { children: "foo" });

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Остаточные параметры исключают некоторые члены объекта

Начиная с текущей версии методы объекта исключаются из механизма остаточных параметров.

ts
class Class { field = ``; method(){ } } function f0<T extends Class>(param: T){ const {...rest} = param; console.log(rest.field); // Ok console.log(rest.method()); // Error -> Property 'method' does not exist on type 'Omit<T, "field" | "method">'.ts(2339) } function f1(param: Class){ const {...rest} = param; console.log(rest.field); // Ok console.log(rest.method()); // Error -> Property 'method' does not exist on type '{}'.ts(2339) }

Аналогичным образом данное поведение распространяется и на деструктуризацию ссылки на экземпляр this.

ts
class Class { field = ``; constructor(){ const { ...rest } = this; console.log(rest.field); // Ok console.log(rest.method()); // Error -> Property 'method' does not exist on type 'Omit<this, "method">'.ts(2339) } method(){ } }

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Постоянная проверка грамматики и биндингов

Начиная с текущей версии TypeScript будет выводить сообщения об грамматических ошибках и ошибках биндинга в JavaScript файлах. Отключить новое поведение можно с помощью указания в начале файла комментарной директивы // @ts-nocheck.