4.6
Выполнение кода в конструкторе потомке до вызова super
Поскольку при расширении одним классом друго
Спецификация ECMScript запрещает потомкам любые операции в которых фигурирует ссылка this
до вызова конструктора своего родителя (super()
), поскольку в этот отрезок времени объект
ещё не существует. TypeScript же, при наличии полей класса и вовсе запретил выполнение любого кода до вызова super()
.
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 } }
Это очень сильно затрудняло сценария при которых данные выступающие в качестве аргументов родительского конструктора предварительно требовали преобразования.
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 после. } }
Сохранение потока управления при деструктуризации размеченных объединений
Дискриминантные свойства позволяют сужать тип до более конкретного..
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 ); // ..с числом } }
..но до текущей версии данный механизм не работал в сочетании с деструкторизацией.
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 } }
Начиная с текущей версии модели создаваемые анализатором потока кода распространяются и на механизм деструктуризации.
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
.
// для параметра function f( { type, value }: Actions){ if ( type === "number" ) { value.toFixed(); // Ok }else if ( type === "string" ) { value.repeat( 2 ); // Ok } }
// для const function f( action: Actions){ const { type, value } = action; if ( type === "number" ) { value.toFixed(); // Ok }else if ( type === "string" ) { value.repeat( 2 ); // Ok } }
// для let function f( action: Actions){ let { type, value } = action; if ( type === "number" ) { value.toFixed(); // Error }else if ( type === "string" ) { value.repeat( 2 ); // Error } }
Улучшение проверки глубоких рекурсии
TypeScript реализует структурную типизацию по которой типы считаются совместимыми на основании совместимости их членов. Эти правила распространяются и на типизированные типы (генерики), что вызывает более сложные вопросы, как например совместимость рекурсивных типов.
interface A<T>{ prop: A<T>; } interface B<T>{ prop: B<T>; } function f(a: A<number>, b: B<string>){ /** * По факту типы параметров a и b не совместимы, * но это невозможно выяснить без рекурсивного равертывания. */ }
Поскольку операции рекурсивного развертывания могут быть очень затратными, алгоритм имеет ограничения на глубину рекурсии, что часто приводит к несовпадению реального с ожидаемым.
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% быстрее).
Улучшение вывода для индексных значений
Начиная с текущей версии стало возможно производить правильный вывод сопоставимых индексных типов.
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 научился разрешать ситуации в которых остаточные параметры представлены размеченными объединениями кортежей.
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
До текущей версии данный код..
export const el = <div>foo</div>;
..преобразовывался в -
import { jsx as _jsx } from "react/jsx-runtime"; export const el = _jsx("div", { children: "foo" }, void 0);
Начиная с текущей версии, конечный код буде лишен фрагмента void 0
.
import { jsx as _jsx } from "react/jsx-runtime"; export const el = _jsx("div", { children: "foo" });
[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Остаточные параметры исключают некоторые члены объекта
Начиная с текущей версии методы объекта исключаются из механизма остаточных параметров.
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
.
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
.