release

3.6

Повышение типобезопасности генераторов

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

ts
// пример со значением возвращающимся из генератора // function generator(): IterableIterator<"Done" | 100> function* generator() { yield 100; return `Done`; } let iterator = generator(); // let iterator: IterableIterator<"Done" | 100> let result = iterator.next(); // let result: IteratorResult<"Done" | 100> /** * Поскольку генератор перешел в завершенное состояние * значение result.value может принадлежать исключительно * к типу string ... */ if (result.done) { /** * ... тем не менее, вывод типов определяет его * как тип объединение (Union) string | number */ let value = result.value; // let value: string | number } ``` `````ts // пример со значением возвращаемым в генератор type Greeter = { greet(): void; }; // function generator(): IterableIterator<undefined> function* generator() { /** * В строке - let greeter: Greeter = yield; * предполагается, что возвращенное из внешнего кода * значение будет принадлежать к типу Greeter... * */ let greeter: Greeter = yield; greeter.greet(); } let iterator = generator(); // let iterator: IterableIterator<undefined> iterator.next(); /** * Строка ниже приведёт к ошибке во время выполнения * внутри генератора при вызове метода greeter.greet(); * поскольку возращенное значение принадлежит к типу number, * в, то время как ожидается тип Greeter. */ iterator.next(123); // Error, runtime error ``` Начиная с версии _TypeScript_ `3.6` описанные выше недостатки были устранены. `````ts // пример со значением возвращающимся из генератора /** * <v3.6: function generator(): IterableIterator<"Done" | 100> * >=v3.6: function generator(): Generator<number, string, unknown> */ function* generator() { yield 100; return `Done`; } /** * <v3.6: let iterator: IterableIterator<"Done" | 100> * >=v3.6: let iterator: Generator<number, string, unknown> */ let iterator = generator(); /** * <v3.6: let result: IteratorResult<"Done" | 100> * >=v3.6: let result: IteratorResult<number, string> */ let result = iterator.next(); if (result.done) { /** * <v3.6: let value: string | number * >=v3.6: let value: string */ let value = result.value; } ``` `````ts // пример со значением возвращаемым в генератор type Greeter = { greet(): void; }; /** * <v3.6: function generator(): IterableIterator<undefined> * >=v3.6: function generator(): Generator<undefined, void, Greeter> */ function* generator() { let greeter: Greeter = yield; greeter.greet(); } /** * <v3.6: let iterator: IterableIterator<undefined> * >=v3.6: let iterator: Generator<undefined, void, Greeter> */ let iterator = generator(); iterator.next(); iterator.next(123); // Error! Argument of type '[123]' is not assignable to parameter of type '[] | [Greeter]'. ``` Подобное стало возможно благодаря добавлению шести новых типов перечисленных ниже, которые вы также можете использовать при работе с генераторами. `````ts interface Iterator<T, TReturn = any, TNext = undefined> { /** ... */ } type IteratorResult<T, TReturn = any> = | IteratorYieldResult<T> | IteratorReturnResult<TReturn>; interface IteratorReturnResult<TReturn> { /** ... */ } interface IteratorYieldResult<TYield> { /** ... */ } interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { /** ... */ } interface GeneratorFunction { /** ... */ } interface GeneratorFunctionConstructor { /** ... */ } ``` И напоследок будет не лишним ещё раз взглянуть на очень простой и информативный пример более эффективной работы с генераторами. `````ts /** * Generator<number, string, boolean> * или по другому * Generator< * возвращаемое с помощью оператора yield значение, * возвращаемое с помощью оператора return значение, * передаваемое в метод next, то есть возвращаемое в генератор, значение * > */ function* counter(): Generator<number, string, boolean> { let i = 0; while (true) { if (yield i++) { console.log( `[if] Block if in counter generator. Variable value "i": ${i}` ); break; } } return 'Done'; } let iterator = counter(); let result = iterator.next(); while (!result.done) { let returnedFromGeneratorValue = result.value; let passedToGeneratorValue = returnedFromGeneratorValue === 3; console.log( `[out] Returned from generator value: ${returnedFromGeneratorValue}` ); console.log(`[in] Passed to generator value: ${passedToGeneratorValue}`); result = iterator.next(passedToGeneratorValue); } console.log( `[end] Return from generator resultant value: ${result.value.toUpperCase()}` ); /** * "[out] Returned from generator value: 0" * "[in] Passed to generator value: false" * "[out] Returned from generator value: 1" * "[in] Passed to generator value: false" * "[out] Returned from generator value: 2" * "[in] Passed to generator value: true" * "[if] Block if in counter generator. Variable value "i": 3" * "[end] Return from generator resultant value: DONE" */ ```

Добавление хелпера имитирующего механизм spread array при компиляции в ecmascript ниже версии 6 при неактивном флаге --downlevelIteration

Начиная с версии v3.6, хелпер, генерирующийся при компиляции такой конструкции, как array spread ([...Array(5)]), в ECMAScript ниже 6 версии, при неактивном флаге --downlevelIteration, претерпел кардинальные изменения. Но обо всем по порядку.

До TypeScript версии v3.6 при компиляции кода в версию ниже es6, для имитации механизма spread применённого к массиву, генерировался код объединяющий массивы с помощью его метода .concat() -

ts
let array = [0, 1, ...[2, 3]]; // этот код es6+ компилировался в.. var array = [0, 1].concat([2, 3]); // ..этот es5 код ``` Но имитация подобным образом не соответствует поведению предполагаемого спецификацией `es6` при использовании массива созданного с помощью конструктора, которому при вызове передали в качестве единственного аргумента числовое значение. `````ts let array = [0, 1, ...Array(3), 2, 3]; // этот код es6+ компилировался в.. var array = [0, 1].concat(Array(2), [2, 3]); // ..этот es5 код ``` Получение экземпляра массива подобным образом приводит к созданию объекта массива с заданной длиной, равной значению переданного в качестве аргумента, а также полным отсутствием элементов и следовательно ассоциированных с ними ключей (индексов массива). `````ts Array(3); // [empty × 3] ['a', 'b', 'c']; // ['a', 'b', 'c'] /** * У объекта массива созданного с помощью конструктора * длина равна переданному аргументу, то есть 3, но * ключи представляющие индекс массива, также как и * элементы - отсутствуют. */ Array(3); // псевдо объект массива {length: 3} /** * У объекта массива созданного с помощью литерала массива * длина равняется количеству его элементов, то есть 3, а также * существуют ключи (0, 1, 2) ассоциированные с элементами ('a','b','c'). */ ['a', 'b', 'c']; // псевдо объект массива {length: 3, 0: 'a', 1: 'b', 2: 'c'} ``` Не будет лишним напомнить, что элементы у массива созданного с помощью конструктора буквально полностью отсутствуют, а не имеют значение `undefined`, как может показаться. При обращении к элементам по индексу входящего в диапазон установленного его длиной, `undefined` возвращается не потому, что хранится в качестве элемента, а потому, что `undefined` возвращается всегда при обращении к несуществующему ключу объекта. `````ts // случай с объектом созданного при помощи литерала ({ prop: 'value' }['prop']); // value - обращение к существующему ключу prop ({}['prop']); // undefined - обращение к не существующему ключу prop // случай с массивом созданного при помощи литерала ['a', 'b', 'c'][0]; // a - обращение к существующему ключу 0 ['a', 'b', 'c'][10]; // undefined - обращение к не существующему ключу 10 // случай с массивом созданного при помощи конструктора Array(3)[0]; // undefined - обращение к не существующему ключу 0 ``` Отсутствие ключей является причиной несоответствия количества реальных итераций и длины массива при работе с ними. Методы массива (`.forEach()`, `.map()` и др.) и итерирующие конструкции (`for...in\for...of`)проверяют ключи с помощью метода `.hasOwnProperty(key)` доставшегося по наследству от базового типа `Object`. `````ts /** * псевдо объект массива {length: 3} * Нет ключей - нет итераций! * Вывод в консоль: (пусто) */ Array(3).forEach(() => console.log(`iteration`)); /** * псевдо объект массива {length: 3} * Нет ключей - нет итераций! * Вывод в консоль: (пусто) */ for (let item of Array(3)) { console.log(`iteration`); } ``` Тем не менее при применении механизма `spread` к массиву в `es6+` создает несуществующие элементы и заполняет их значениями `undefined`. `````ts /** * Длина === 7, количество итераций === 7 */ [1, 2, ...Array(3), 3, 4]; // es6 - [1, 2, undefined, undefined, undefined, 3, 4] ``` Но скомпилированный `TypeScript` в `es < v6` код при неактивном флаге `--downlevelIteration`, до версии `v3.6` не соответствовал этому поведению, поскольку не преобразовывал отсутствующие элементы в элементы ассоциированные со значением `undefined`. `````ts /** * Длина === 7, количество итераций === 4! */ [1, 2, ...Array(3), 3, 4]; // es6 синтаксис компилировался в.. [1, 2].concat(Array(3), [2, 3]); // в es5 подобный код - [1, 2, empty × 3, 2, 3] ``` После того, как комьюнити обратило внимание на несоответствие в поведении, подход с нативным методом `.concat()` заменили на генерацию хелпера `__spreadArrays(array)` и тем самым исправили поведение на предполагаемое спецификацией. Отказ от нативного `.concat()` замедлил сказался на производительности, снизив её ровно в два раза.

##Расширение системы типов типами представляющими структуры данных только для чтения

Начиная с версии v3.6, система типов Typescript пополнилась новыми вспомогательными типами представляющими структуры данных только для чтения (readonly). Такими типами стали ReadonlyMap<K, V> и ReadonlySet<T> и в отличии от своих прототипов они не имеют методов способных их изменить.

ts
let map: ReadonlyMap<string, number> = new Map([['key', 0]]); let set: ReadonlySet<number> = new Set([0, 1, 2]); ```

##Строковой идентификатор функции-конструктора

TypeScript, начиная с версии v3.6 реализовывает механизм ecmascript спецификации предусматривающей строковое именование функции-конструктора. Другими словами, определение метода идентификатор которого представлен в виде строки эквивалентной "constructor" расценивается как определение функции-конструктора. Если идентификатор помечается, как вычисляемый ["constructor"], то такое объявление расценивается обычным методом класса.

ts
// этот ts код... class T { constructor() { console.log(`Constructor!`); } ['constructor']() { console.log(`Method with name "constructor"!`); } } let t = new T(); // output: Constructor! t.constructor(); // output: Method with name "constructor"! // ...будет скомпилирован в этот js код class T { constructor() { console.log(`Constructor!`); } ['constructor']() { console.log(`Method with name "constructor"!`); } } let t = new T(); // output: Constructor! t.constructor(); // output: Method with name "constructor"! ```

##Расширение вывода об ошибках при работе с Promise

Поскольку работа с Promise является очень важной частью повседневной разработки, TypeScript, начиная с версии v3.6, расширила информирование при возникновении ошибок связанными с ними, а также били добавлены механизмы их быстрого устранения.

ts
interface Person { name: string; age: number; } declare function getPersonData(): Promise<Person>; declare function printPersonInfo(personData: Person): void; async function main() { /** * До v3.6 возникала ошибка говорящая, что * тип Promise<Person> не соответствует типу Person * * Argument of type 'Promise<Person>' is not assignable * to parameter of type 'Person'. * Type 'Promise<Person>' is missing the following properties * from type 'Person': name, age ts(2345) * * * После v3.6 вывод расширили предложением пофиксить * ошибку добавлением ключевого слова await * * Did you forget to use 'await'? * */ printPersonInfo(getPersonData()); // Error printPersonInfo(await getPersonData()); // Ok } async function getPersonData(): Promise<Person> { /** * Также предлагается пофиксить ошибку путем * добавления ключевого слова await * * Property 'json' does not exist on type 'Promise<Response>'.ts(2339) * Did you forget to use 'await'? */ return fetch(``).json(); // Error return (await fetch(``)).json(); // Ok } ```

##Слияние одноименных классов и функций из окружающего контекста

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

ts
declare class Point { x: number; y: number; constructor(x: number, y: number); } declare function Point(x: number, y: number): Point; ``` Начиная с `v3.7` компилятор будет прибегать к данной возможности при генерации `.d.ts` файлов из `JavaScript` кода.

##Добавлена поддержка Unicode для идентификаторов

Начиная с версии v3.6 в TypeScript, для кода компилируемого в --target es2015 и выше, была добавлена поддержка символов unicode используемых в качестве идентификаторов синтаксических конструкций.

ts
/** * До v3.6: * Error, Variable declaration expected. * * Начиная с v3.6 для --target es2015 и выше * Ok */ const 𝓱𝓮𝓵𝓵𝓸 = 'world'; ```

##Поддержка import.meta для SystemJS

В TypeScript v3.6 была добавлена возможность трансформации import.meta в context.meta, что является необходимым функционалом при работе с такой модульной системой, как SystemJS (--module system).

ts
// этот код .ts ... console.log(import.meta); // ...преобразуется в этот .js код System.register([], function (exports_1, context_1) { 'use strict'; var __moduleName = context_1 && context_1.id; return { setters: [], execute: function () { console.log(context_1.meta); }, }; }); ```

##get и set в окружающем контексте

Начиная с версии v3.6, TypeScript получил возможность объявлять get/set в окружающем контексте (declare class ID {} или в файлах декларациях .d.ts).

ts
// Начиная с v3.6 - Ok declare class T { get field(): string; set field(value: string): void; } ``` Кроме того, начиная с версии `v3.7` компилятор начнет использовать данный функционал при генерации `.d.ts` файлов.

##Api для поддержки --build и --incremental

С недавних пор в TypeScript, благодаря добавлению функционала скрывающегося за флагами --build и --incremental, появилась возможность создавать ссылки на другие проекты и генерировать метаинформацию о предыдущих компиляциях, позволяющую включать в последующие компиляции только те файлы, которые подверглись изменениям. Данный функционал позволил значительно увеличить скорость разработки, но до этого момента не был доступен для совместного использования с такими сборщиками, как например Gulp или Webpack. Начиная с версии v3.6, разработчики компилятора TypeScript добавили новое api позволяющее создателям плагинов для сторонних сборщиков использовать данный, увеличивающий скорость компиляции, механизм.

##Интеллектуальное определение синтаксиса авто-импорта

Поскольку TypeScript позволяет напрямую работать с JavaScript кодом, который может содержать множество вариантов экспорта конструкций (стандарты ECScript, CommonJS, AMD, SystemJS), возникают разногласия по поводу определения синтаксиса экспорта в коде .ts при авто-импорте. Начиная с версии v3.6, компилятору была добавлена возможность автоматического определения синтаксиса используемого при авто-импорте для каждого отдельного случая.