3.6
Повышение типобезопасности генероторов
До текущей версии такие конструкции как генераторы (generators
) имели недоработки косающиеся определения типа данных возвращаемых, как из, так и во внутрь генератора, значений.
// пример со значением возвращающимся из генератора
// 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()
-
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()` и др.) и итерирующие конструкции (`forin\forof`)проверяют ключи с помощью метода `.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>
и в отличии от своих прототипов они не имеют методов способных их изменить.
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 код...
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
, расширила информирование при возникновении ошибок связанными с ними, а также били добавлены механизмы их быстрогоустранения.
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, agets(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
был реализован механизм позволяющий сливать воедино одноименные классы и функции находящиеся в одном окружающем контексте.
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 испольуемых в качестве идентификаторов синтаксических конструкций.
/**
* До 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 ...
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
).
// Начиная с 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, SysytemJS), возникают разногласия по поводу определения синтаксиса экспорта в коде .ts
при авто-импорте. Начиная с версии v3.6
, компилятору была добавлена возможность автоматического определения синтаксиса используемого при авто-импорте для каждого отдельного случая.