4.4
Анализ потока для результата условного выражения
ДЛя конкретизации типа очень часто приходится прибегать к такому механизму, как защитники типа
.
function f(param: unknown) { if(typeof param === "string"){ // компилятор понимает, что в этом блоке кода param принадлежит к типу string param.toLocaleLowerCase(); } }
Поскольку анализ потока управления понимает, к какому типу принадлежит идентификатор параметра в конкретном блоке кода, механизм прекрасно справляется со своей задачей, но все же у него существует один значительный недостаток - результат проверки нельзя вынести за пределы условного выражения.
function f(param: unknown) { const isString = typeof param === "string"; if(isString){ param.toLocaleLowerCase(); // Ошибка! Компилятор не понимает, что param принадлежит к типу string } }
Особенно этот досадный факт заметен при многократном использовании механизма защитника типа.
По этой причине, начиная с текущей версии, поведение анализа потока управления было переработано таким образом, чтобы механизм защитника типа полноценно работал с результатами выражений выполненных вне условных конструкций. Единственное условие заключается в том, что значение условного выражение должно быть неизменяемо, то есть ассоциировано с const
или readonly
.
function f(param: unknown) { const isString = typeof param === "string"; if(isString){ param.toLocaleLowerCase(); // Ok - начиная с версии 4.4! } }
Стоит заметить, что новый механизм справляется и с более сложными условиями, как например проверка существования значений множества параметров функции.
function f(p0: string | undefined, p1: string | undefined) { // Выполняем проверку на существование значения обоих параметров const isParamsExists = p0 && p1; if(isParamsExists){ p0.toLocaleLowerCase(); // Ok p1.toLocaleLowerCase(); // Ok // Ошибки не возникаетпоскольку помилятор понимает, что isParamsExists содержится результат подтверждающие наличие значения у обоих параметров } }
Помимо этого, новый анализ работает транзитивно, то есть, компилятор будет "распутывать" цепочку выполненных проверок.
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 } }
Этот механизм отлично справляется и с дескременантными объединениями
.
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(); } }
Относительно дискриминантных объединений этот механизм остается работать даже при использовании объектной деструктуризации.
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
.
// Пример для symbol type Colors = { [color: symbol]: number; } const red = Symbol('red'); const colors: Colors = {}; colors[red] = 255; // Ok const redColorValue = colors[red]; // const redColorValue: number
// пример для 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
.
// эта конструкция автоматически будети преобразована в... type T = { [field: string | number]: boolean; } // ...эту конструкцию. type T0 = { [field: string]: boolean; [field: number]: boolean; }
Изменение типа ошибки в блоке catch и флаг --useUnknownInCatchVariables
Поскольку с помощью throw
может быть выброшено исключение любого типа, в ранних версиях TypeScript ошибка из блока catch
принадлежала к типу any
. Но так как тип any
позволяет обращаться в коде к несуществующим членам объекта, это может привести к ошибкам вовремя выполнения.
// ранние версии function f(){ throw 5; // выкидываем код исключения... } try { f(); }catch(error){ // error: any console.log(error.message); // ...но по ошибке обращаемся к свойству message определенного в объекте Error }
Поэтому, начиная с текущей версии, тип ошибки в блоке catch
изменили с типа any
, на тип unknown
, а для сохранения старого поведения ввели новый флаг компилятора --useUnknownInCatchVariables
.
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
, его также можно изменить в строго заданных местах, при помощи явного указания.
function f(){ throw 5; // выкидываем код исключения... } try { f(); }catch(error: any){ console.log(error.message); // ... Ok на этапе компиляции, но ошибка возникнет во время выполнения! }
Типы необязательных свойств теперь более точные
В TypeScript необязательное поле объекта расценивается, как тип объединение включающего тип undefined
.
// это тип рассматривается, как... type T = { a: number; b?: string; } // ...как этот тип type T = { a: number; b: string | undefined; }
Это означает, что необязательному полю объекта, помимо значения принадлежащего к конкретному типу, можно также присвоить значение undefined
.
type T = { a: number; b?: string; } let o: T = { a: 5, b: undefined // Ok };
Но поскольку значение undefined
присвоенное полю объекта далеко не то же самое, что отсутствие члена вовсе, при котором также возвращается undefined
, начиная с текущей версии, при активации нового флага --exactOptionalPropertyTypes
, в подобных случаях будут возникать ошибки.
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
после которого следуют открывающая и закрывающая фигурная скобка {}
.
class T { // статический блок static { // здесь можно выполнять обращение к статическим членам класса } }
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
.
class T { static { const VALUE = 8080; if(/**some condition */){ }else{ } try { // ... }catch(error){ // ... } } }
К тому же, тело одного класса может включать произвольное количество статических блоков, которые будут выполняться в порядке объявления.
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
, ссылалось на сам объект.
// раньше let o = { f(){ console.log(this); } } o.f(); // o
Начиная с текущей версии поведение измененно на то, что присуще es
модулям.
// теперь let o = { f(){ console.log(this); } } o.f(); // f
[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Изменение проверки await для функций
До текущей версии TypeScript всеми силами старался не допускать ошибок вызываемых вызовом асинхронных функций без ключевого слова await
. Но механизм был реализован не полностью, поскольку работал только при сохранении результата вызова асинхронной функции в переменную.
// До теккущей версии 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
непосредственно в условном выражении ошибки не возникало.
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
не могут быть инициализированы.
abstract class T { /** * Ok раньше и * Error теперь - * Property 'field' cannot have an initializer because it is marked abstract.(1267) */ abstract field = 5; // Ok раньше и Error начиная с этой версии }
Теперь, абстрактные поля ограничиваются одним лишь объявлением с указанием типа.
abstract class T { abstract field: number; // Ok }