release

4.2

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

Начиная с текущей версии вводятся ограничения на указание в кортеже остаточных типов (...rest). Теперь, в случае если перед остаточным типом указан другой остаточный тип или после него следует необязательный тип ([number?]), возникнет ошибка.

ts
/** * [0] A rest element cannot follow another rest element.ts(1265) * */ let v0: [...boolean[], ...string[]]; // Error [0] let v1: [...boolean[], boolean, ...string[]]; // Error [0] let v2: [...boolean[], number]; // Ok let v3: [number, ...boolean[]]; // Ok let v4: [number, ...boolean[], number]; // Ok /** * [0] An optional element cannot follow a rest element.ts(1266) * */ let v5: [...boolean[], boolean?]; // Error [1] let v6: [boolean?, ...boolean[]]; // Ok

Изменение вывода для псевдонимов типов

До текущей версии, псевдонимы типов, в разных ситуациях, выводились по-разному. Если тип значения представлялся псевдонимом типа, то таковым оно и определялось при выводе информации в консоль или подсказках ide. Но когда псевдоним типа участвует при определении другого типа, то выводится типом(и) для которого он был назначен, то есть разворачивается.

ts
type ValueType = number | string | boolean; /** * Тип параметра value выводится как псевдоним типа, * который в выводе типа возвращаемого значения разворачивается. * function action(value: ValueType): string | number | boolean | undefined * */ function action(value: ValueType){ if(Math.random() < 0.5){ return undefined; } return value; } /** * Подобное поведение аналогично и для любых * других похожих случаев. * * let v0: ValueType * let v1: string | number | boolean | undefined */ declare let v0: ValueType; let v1 = Math.random() < 0.5 ? v0 : undefined;

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

ts
type ValueType = number | string | boolean; /** * Псевдоним типа не разворачивается * function action(value: ValueType): ValueType | undefined * */ function action(value: ValueType){ if(Math.random() < 0.5){ return undefined; } return value; } /** * Подобное поведение аналогично и для любых * других похожих случаев. * * let v0: ValueType * let v1: ValueType | undefined */ declare let v0: ValueType; let v1 = Math.random() < 0.5 ? v0 : undefined;

Ужесточение правил проверки для in-оператора

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

ts
"hasOwnProperty" in {}; // Ok "hasOwnProperty" in 0; // Error -> The right-hand side of an 'in' expression must not be a primitive.ts(2361)

Новый флаг --noPropertyAccessFromIndexSignature

После того, как несколько версий назад в TypeScript было разрешено обращаться к динамическим полям объекта не только при помощи скобочной (o['field']), но и точечной нотации (o.field), стала очевидна проблема связанная с необязательными полями определенных в том же объекте. Другими словами, потерян механизм отделения предопределенных полей, от динамических.

ts
type Settings = { env?: string[]; // определение необязательного предопределенного поля [key: string]: any; // определение динамических полей } function configurate(settings: Settings){ /** * [0] хотя поле env является предопределенным, * ошибки не возникает, поскольку компилятор предполагает, *, что обращение происходит к динамическому свойству envs. * * Отсюда выходит, что нет способа защитится от ошибки. * * [1] обращение к динамическому свойству. * [2] во всех случаях допустимо обращение, * как через скобочную, так и точечную нотацию. */ //---------------------------- if(settings.envs){ // Ok [0] } if(settings['envs']){ // Ok [0] } //---------------------------- if(settings.env){ // Ok [0] } if(settings['env']){ // Ok [0] } }

Для исправления этого был добавлен флаг --noPropertyAccessFromIndexSignature, активация которого возвращает старое поведение. То есть, обращение с помощью точечной нотации допускается только для предопределённых полей.

ts
type Settings = { env?: string[]; // определение необязательного предопределенного поля [key: string]: any; // определение динамических полей } function configurate(settings: Settings){ /** * [0] Property 'envs' comes from an index signature, so it must be accessed with ['envs'].ts(4111) * * * [1] обращение к предопределенному свойству. * [2] при помощи скобочной нотации можно обращаться ко всем полям. * Точечная нотация только для предопределенных полей. */ //---------------------------- if(settings.envs){ // Error [0] } if(settings['envs']){ // Ok [0] } //---------------------------- if(settings.env){ // Ok [1] } if(settings['env']){ // Ok [1] } }

Модификатор abstract для описания типа конструктора

Абстрактные классы предназначены исключительно для расширения (невозможно создать его экземпляр с помощью оператора new), а его абстрактные члены должны обязательно должны быть переопределены потомками.

ts
/** * Абстрактный класс с одним абстрактным методом. */ abstract class Shape { abstract getRectangle(): ClientRect; } /** * Из-за того, что класс абстрактный не получится создать его экземпляр с помощью оператора new. */ new Shape(); // Error -> Cannot create an instance of an abstract class.ts(2511) /** * [0] Кроме этого, подкласс обязательно должен переопределять абстрактные члены своего суперкласса. */ class Circle extends Shape { getRectangle(){// [0] return { width:0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }; } }

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

ts
interface IHasRectangle { getRectangle(): ClientRect; } type HasRectangleClass = new() => IHasRectangle; /** * [*] Type 'typeof Shape' is not assignable to type 'HasRectangleClass'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2322) */ let ClassType: HasRectangleClass = Shape; // Error [*]

Кроме этого, невозможно получить тип экземпляра абстрактного класса с помощью вспомогательного типа InstanceType<T>.

ts
/** * [*] Type 'typeof Shape' does not satisfy the constraint 'new (...args: any) => any'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2344) */ type Instance = InstanceType<typeof Shape>; // Error [*]

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

ts
function subclassCreator(Base: new() => IHasRectangle){ return class extends Base { getRectangle(){ return { width:0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }; } } } /** * [*] Argument of type 'typeof Shape' is not assignable to parameter of type 'new () => IHasRectangle'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345) */ subclassCreator(Shape); // Error [*] -> передача в качестве аргумента абстрактный класс subclassCreator(Circle); // Ok -> передача в качестве аргумента конкретный класс

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

ts
interface IHasRectangle { getRectangle(): ClientRect; } type HasRectangleClass = abstract new() => IHasRectangle; let ClassType: HasRectangleClass = Shape; // Ok

Несмотря на то, что тип класса имеет абстрактный модификатор, он также остается совместимым с типами конкретных классов.

ts
function subclassCreator(Base: abstract new() => IHasRectangle){ return class extends Base { getRectangle(){ return { width:0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }; } } } subclassCreator(Shape); // Ok -> абстрактный класс subclassCreator(Circle); // Ok -> конкретный класс

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

ts
type AbstractInstanceType<T extends abstract new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any; type Instance = AbstractInstanceType<typeof Shape>; // Ok

Понимание структуры проекта с флагом --explainFiles

У TypeScript разработчиков часто возникает вопрос, почему в сборку был включён тот или иной файл, даже если он был добавлен в exclude. Поэтому, для лучшего понимания работы компилятора tsc был добавлен новый флаг --explainFiles позволяющий выводить информацию о зависимостях.

ts
tsc --explainFiles

Помимо вывода в консоль, данный флаг позволяет записывать информацию в файл, или даже открывать в visual studio code.

bash
// вывод в файл tsc --explainFiles > explanation.txt // вывод в редактор vsc tsc --explainFiles | code -

Проверка вызова функций в логических выражениях

Теперь, проверка вызова функций доступна и для логических выражений с применением операторов && и ||.

ts
function shouldDisplayElement(element: Element) { return true; } function getVisibleItems(elements: Element[]) { /** * [*] Error -> This condition will always return true since the function is always defined. * Did you mean to call it instead? */ return elements.filter(e => shouldDisplayElement /**[*] */ && e.children.length); }

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

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

ts
/** * Версия <4.2 */ type Magic = { fire?: string[]; water?: string[]; } declare const HERO_CONFIG: Magic; /** * [*] Type 'Magic' is not assignable to type '{ [key: string]: string[]; }'. * * Ошибка возникает потому, что тип переменной hero * предполагает обязательные свойства, в, то время, как объект * HERO_CONFIG - необязательные, а значит и возможно отсутствующие. * */ const hero: {[key: string]: string[]} = HERO_CONFIG; /**[*]*/

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

ts
/** * Версия >=4.2 */ type Magic = { fire?: string[]; water?: string[]; } declare const HERO_CONFIG: Magic; const hero: {[key: string]: string[]} = HERO_CONFIG; /**Ok*/

Единственное стоит уточнить, что ошибка по-прежнему возникает при попытке инициализировать необязательные поля значением undefined...

ts
/** * Версия >=4.2 * * [0] поля определены как обязательные! */ type Magic = { fire?: string[]; water?: string[]; } declare const HERO_CONFIG: Magic; /** * [1] Error -> * Type 'undefined' is not assignable to type 'string[]'. */ const hero: {[key: string]: string[]} = HERO_CONFIG; hero['fire'] = ['fireball']; // Ok hero['water'] = undefined; // Error [1]

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

ts
/** * Версия >=4.2 * * [0] поля определены как обязательные! */ type Magic = { fire: string[] | undefined; // [0] water: string[] | undefined; // [0] } declare const HERO_CONFIG: Magic; /** * [1] Error -> * Type 'Magic' is not assignable to type '{ [key: string]: string[]; }'. */ const hero: {[key: string]: string[]} = HERO_CONFIG; /**[1]*/

И кроме этого, данные правила применимы исключительно к строковой индексной сигнатуре.

ts
/** * Версия >=4.2 * * [0] ключи полей определены как индексы! */ type Port = { 80?: string; // [0] 88?: string; // [0] } declare const SERVER_PORT: Port; /** * [2] Ключ индексной сигнатуры принадлежит к типу number. * * [1] Error -> * Type 'Port' is not assignable to type '{ [key: number]: string[]; }'. */ const port: {[key: number]: string[]} = SERVER_PORT; /**[1] */ /**[2] */

Деструктурированные переменные можно явно пометить как неиспользуемые

До этого момента, при активном флаге --noUnusedLocals, неиспользуемая переменная объявленная в процессе деструктуризации вызывала ошибку.

ts
function f(){ /** * [*] Error -> 'first' is declared but its value is never read. */ let [first /** [*] */, second] = [0, 1]; return second; }

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

ts
function f(){ let [_first /** Ok */, second] = [0, 1]; return second; }

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Обновление lib.d.ts

Библиотека lib.d.ts претерпела множество изменений, самые значительные из которых коснулись таких типов, как Intl и ResizeObserver.

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

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

ts
function* g0 () { /** * Версия <4.2 -> Ok * Версия >=4.2 -> Error * 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. */ const value = yield 1; // Error } function* g1 () { /** * Версия <4.2 -> Ok * Версия >=4.2 -> Ok * Потому, что значение не используется. */ yield 1; } function* g2 () { /** * Версия <4.2 -> Ok * Версия >=4.2 -> Ok * Потому, что явно задан тип string. */ const value: string = yield 1; } function* g3 (): Generator<number, void, string> { /** * Версия <4.2 -> Ok * Версия >=4.2 -> Ok * Потому, что тип указан в возвращаемом типе. */ const value = yield 1; }

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Проверка вызова функций в условных операторах приурочена к флагу --strictNullChecks

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

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Лимит для механизма spread в кортежах

Поскольку механизм распространения (spread) в кортежах может привести к зависанию компилятора, было принято решение, установить ограничение глубины в значение 10.000.

ts
const tuple = [0, '', true] as const; // инициализация кортежа с тремя значениями const doubleTuple = [...tuple, ...tuple]; // инициализация кортежа с помощью механизма распространения, который приведет к удваиванию значений

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Изменение правил для оператора in

Изменение поведения оператора in, о котором было рассказано ранее, могут привести к ошибкам в существующем коде.

ts
"hasOwnProperty" in {}; // Ok "hasOwnProperty" in 0; // Error -> The right-hand side of an 'in' expression must not be a primitive.ts(2361)

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Расширение .d.ts теперь нельзя использовать в путях импорта

Начиная с текущей версии, при импорте файла с расширением .d.ts возникает ошибка.

ts
import {IAction} from "./types.d.ts"; // Error

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] Изменение типа параметра функции обратного вызова передаваемой в качестве аргумента функции visitNode

В TypeScript есть функция visitNode, сигнатура которой определяет параметр функцию обратного вызова lift, тип параметра которой, теперь, вместо NodeArray<Node>, ожидает readonly Node[].

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

В версии 4.2@beta было предложено поведение литерального вывода шаблона, которое в релизе было отменено.