release

4.4

Анализ потока для результата условного выражения

ДЛя конкретизации типа очень часто приходится прибегать к такому механизму, как защитники типа.

ts
function f(param: unknown) { if(typeof param === "string"){ // компилятор понимает, что в этом блоке кода param принадлежит к типу string param.toLocaleLowerCase(); } }

Поскольку анализ потока управления понимает, к какому типу принадлежит идентификатор параметра в конкретном блоке кода, механизм прекрасно справляется со своей задачей, но все же у него существует один значительный недостаток - результат проверки нельзя вынести за пределы условного выражения.

ts
function f(param: unknown) { const isString = typeof param === "string"; if(isString){ param.toLocaleLowerCase(); // Ошибка! Компилятор не понимает, что param принадлежит к типу string } }

Особенно этот досадный факт заметен при многократном использовании механизма защитника типа. По этой причине, начиная с текущей версии, поведение анализа потока управления было переработано таким образом, чтобы механизм защитника типа полноценно работал с результатами выражений выполненных вне условных конструкций. Единственное условие заключается в том, что значение условного выражение должно быть неизменяемо, то есть ассоциировано с const или readonly.

ts
function f(param: unknown) { const isString = typeof param === "string"; if(isString){ param.toLocaleLowerCase(); // Ok - начиная с версии 4.4! } }

Стоит заметить, что новый механизм справляется и с более сложными условиями, как например проверка существования значений множества параметров функции.

ts
function f(p0: string | undefined, p1: string | undefined) { // Выполняем проверку на существование значения обоих параметров const isParamsExists = p0 && p1; if(isParamsExists){ p0.toLocaleLowerCase(); // Ok p1.toLocaleLowerCase(); // Ok // Ошибки не возникаетпоскольку помилятор понимает, что isParamsExists содержится результат подтверждающие наличие значения у обоих параметров } }

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

ts
function f(param: string | number | boolean) { const isString = typeof param === "string"; const isNumber = typeof param === "number"; const isStringOrNumber = isString || isNumber; if(isStringOrNumber){ param; // string | number }else{ param; // boolean } }

Этот механизм отлично справляется и с дескременантными объединениями.

ts
type Animal = | {kind: "bird"; fly(): void;} | {kind: "fish"; swim(): void;} function move(animal: Animal){ const isBird = animal.kind === "bird"; if(isBird){ animal.fly(); }else{ animal.swim(); } }

Относительно дискриминантных объединений этот механизм остается работать даже при использовании объектной деструктуризации.

ts
type Animal = | {kind: "bird"; fly(): void;} | {kind: "fish"; swim(): void;} function move(animal: Animal){ const {kind} = animal; if(kind === "bird"){ animal.fly(); }else{ animal.swim(); } }

Типы symbol и literal template string в индексной сигнатуре

Начиная с текущей версии TypeScript позволяет указывать в индексной сигнатуре такие типы, как symbol и literal template string.

ts
// Пример для symbol type Colors = { [color: symbol]: number; } const red = Symbol('red'); const colors: Colors = {}; colors[red] = 255; // Ok const redColorValue = colors[red]; // const redColorValue: number
ts
// пример для literal template string type Attrs = { width: number; height: number; [dataAttr: `data-${string}`]: string; } const attrs: Attrs = { width: 100, height: 100, ["data-color"]: "#fff", // Ok ["bibbody-bobbody"]: "vzhuh", // Error };

Помимо этого, для множественного объявления индексной сигнатуры был добавлен так называемый синтаксический сахар в лице типа union.

ts
// эта конструкция автоматически будети преобразована в... type T = { [field: string | number]: boolean; } // ...эту конструкцию. type T0 = { [field: string]: boolean; [field: number]: boolean; }

Изменение типа ошибки в блоке catch и флаг --useUnknownInCatchVariables

Поскольку с помощью throw может быть выброшено исключение любого типа, в ранних версиях TypeScript ошибка из блока catch принадлежала к типу any. Но так как тип any позволяет обращаться в коде к несуществующим членам объекта, это может привести к ошибкам вовремя выполнения.

ts
// ранние версии function f(){ throw 5; // выкидываем код исключения... } try { f(); }catch(error){ // error: any console.log(error.message); // ...но по ошибке обращаемся к свойству message определенного в объекте Error }

Поэтому, начиная с текущей версии, тип ошибки в блоке catch изменили с типа any, на тип unknown, а для сохранения старого поведения ввели новый флаг компилятора --useUnknownInCatchVariables.

ts
function f(){ throw 5; // выкидываем код исключения... } try { f(); }catch(error){ // error: unknown console.log(error.message); // ... Error поскольку у типа unknown нет свойства message // требуется явное выяснение принадлежности к типу if(error instanceof Error){ console.log(error.message); // Ok } }

Помимо того, что существует возможность вернуть тип any глобально при помощи флага --useUnknownInCatchVariables входящего в группировку strict, его также можно изменить в строго заданных местах, при помощи явного указания.

ts
function f(){ throw 5; // выкидываем код исключения... } try { f(); }catch(error: any){ console.log(error.message); // ... Ok на этапе компиляции, но ошибка возникнет во время выполнения! }

Типы необязательных свойств теперь более точные

В TypeScript необязательное поле объекта расценивается, как тип объединение включающего тип undefined.

ts
// это тип рассматривается, как... type T = { a: number; b?: string; } // ...как этот тип type T = { a: number; b: string | undefined; }

Это означает, что необязательному полю объекта, помимо значения принадлежащего к конкретному типу, можно также присвоить значение undefined.

ts
type T = { a: number; b?: string; } let o: T = { a: 5, b: undefined // Ok };

Но поскольку значение undefined присвоенное полю объекта далеко не то же самое, что отсутствие члена вовсе, при котором также возвращается undefined, начиная с текущей версии, при активации нового флага --exactOptionalPropertyTypes, в подобных случаях будут возникать ошибки.

ts
type T = { a: number; b?: string; } let o: T = { a: 5, b: undefined // Error -> Type 'undefined' is not assignable to type 'string'.ts(2322) };

Статические блоки в классах

Вслед за спецификацией ECMAScript, TypeScript реализует механизм обозначаемый, как статические классовые блоки, которые позволяют работать со статическими членами в момент определения самого класса. Статические блоки объявляются при помощи ключевого слова static после которого следуют открывающая и закрывающая фигурная скобка {}.

ts
class T { // статический блок static { // здесь можно выполнять обращение к статическим членам класса } }
ts
class Operators { static #isLock = false; static get isLock(){ return Operators.#isLock; } static readonly Plus = new Operators("+"); static readonly Minus = new Operators("-"); // статический блок static { Operators.#isLock = true; } constructor(readonly operator: string){ if(Operators.isLock){ throw "creating instances is prohibited!"; } } }

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

ts
class T { static { const VALUE = 8080; if(/**some condition */){ }else{ } try { // ... }catch(error){ // ... } } }

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

ts
class T { static { console.log(0); } static { console.log(1); } static { console.log(2); } } /** * console output * > 0 * > 1 * > 2 */

Модернизация вывода tsc --help

Вывод информации в консоль подвергся значительному улучшению. Теперь он стал более информативным и понятным.

Улучшение производительности

В текущей версии значительное ускорение получили такие механизмы, как - генерация деклараций .d.ts, нормализация путей, отображение путей, инкрементальные сборки --incremental, создание карты, а также быстрые сборки --force. Кроме этого, появился механизм подсказок предназначенный для работы с .js файлами, подсказок по коду, а также отображение истинных путей при автоимпорте.

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Изменение поведения контекста для функций

До текущей версии, ключевое слово this внутри функций объявленных в литерале объекта, при компиляции в модули CommonJs или AMD, ссылалось на сам объект.

ts
// раньше let o = { f(){ console.log(this); } } o.f(); // o

Начиная с текущей версии поведение измененно на то, что присуще es модулям.

ts
// теперь let o = { f(){ console.log(this); } } o.f(); // f

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Изменение проверки await для функций

До текущей версии TypeScript всеми силами старался не допускать ошибок вызываемых вызовом асинхронных функций без ключевого слова await. Но механизм был реализован не полностью, поскольку работал только при сохранении результата вызова асинхронной функции в переменную.

ts
// До теккущей версии async function validate(): Promise<boolean> { return false; } async function a(): Promise<string> { const isExpressionValid = validate(); /** * [*] This condition will always return true since this 'Promise<boolean>' is always defined.ts(2801) */ if (isExpressionValid) { // [*] return "true"; } return "false"; }

Однако при вызове асинхронной функции без указания ключевого слова await непосредственно в условном выражении ошибки не возникало.

ts
async function validate(): Promise<boolean> { return false; } async function a(): Promise<string> { const isExpressionValid = validate(); /** * [*] * Ok до текущей версии * Начиная с текущей версии - * This condition will always return true since this 'Promise<boolean>' is always defined.ts(2801) */ if (validate()) { // [*] return "true"; } return "false"; }

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

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Абстрактные поля больше не могут быть инициализированы

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

ts
abstract class T { /** * Ok раньше и * Error теперь - * Property 'field' cannot have an initializer because it is marked abstract.(1267) */ abstract field = 5; // Ok раньше и Error начиная с этой версии }

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

ts
abstract class T { abstract field: number; // Ok }