Команда TypeScript сообщила 16 августа о выпуске предварительной версии (release candidate) TypeScript 3.6. В течение нескольких недель команда планирует стабилизировать предварительную версию. После этого TypeScript 3.6 выйдет официально.
Релиз кандидат доступен на NuGet. Также его можно установить через npm.
npm install -g typescript@rc
Есть сборки для Visual Studio, Visual Studio Code и Sublime Text.
Давайте посмотрим, какие возможности предлагает TypeScript 3.6.
В новой версии TypeScript предусмотрена строгая проверка функций итераторов и генераторов. В ранних версиях пользователи генераторов не могли определить, было ли значение возвращено из генератора или определено.
function* foo() {
if (Math.random() < 0.5) yield 100;
return "Finished!"
}
let iter = foo();
let curr = iter.next();
if (curr.done) {
// В TypeScript 3.5 и более ранних версиях здесь ожидалось 'string | number'.
curr.value
}
Также генераторы ожидали, что типом yield
по умолчанию является any
.
function* bar() {
let x: { hello(): void } = yield;
x.hello();
}
let iter = bar();
iter.next();
iter.next(123); // oops! runtime error!
TypeScript 3.6 ожидает, что в первом примере curr.value
имеет тип string
. Это возможно благодаря изменениям в определении типов в Iterator
и IteratorResult
. Также появился новый тип для представления генераторов: Generator
.
Iterator
в TypeScript 3.6 позволяет пользователям указывать тип принимаемых и возвращаемых данных, а также тип данных, которых принимает next
.
interface Iterator {
// Принимает 0 или 1 аргумент - не принимает 'undefined'
next(...args: [] | [TNext]): IteratorResult;
return?(value?: TReturn): IteratorResult;
throw?(e?: any): IteratorResult;
}
Новый тип Generator
всегда итерабельный, также у него всегда есть методы return
и throw
.
interface Generator
extends Iterator {
next(...args: [] | [TNext]): IteratorResult;
return(value: TReturn): IteratorResult;
throw(e: any): IteratorResult;
[Symbol.iterator](): Generator;
}
TypeScript 3.6 конвертирует IteratorResult
в различаемое объединение. Это позволяет определять разницу между получаемыми и возвращаемыми данными.
type IteratorResult = IteratorYieldResult | IteratorReturnResult;
interface IteratorYieldResult {
done?: false;
value: TYield;
}
interface IteratorReturnResult {
done: true;
value: TReturn;
}
В TypeScript 3.6 используется yield
в теле функции-генератора. Это позволяет корректно представлять данные, которые передаются в генератор в результате вызова next()
.
function* foo() {
let x: string = yield;
console.log(x.toUpperCase());
}
let x = foo();
x.next(); // первый вызов 'next' всегда игнорируется
x.next(42); // error! ожидается 'string', получено 'number'
Также в TypeScript 3.6 можно явно указывать тип данных, полученных из yield expression
. В примере ниже next()
можно выбрать только с типом данных boolean
. В зависимости от значения done
, value
может быть строкой или числом.
/**
* - получает на вход числа
* - возвращает строки
* - можно передать в boolean
*/
function* counter(): Generator {
let i = 0;
while (true) {
if (yield i++) {
break;
}
}
return "done!";
}
var iter = counter();
var curr = iter.next()
while (!curr.done) {
console.log(curr.value);
curr = iter.next(curr.value === 5)
}
console.log(curr.value.toUpperCase());
// вывод:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!
Детали можно найти в пулреквесте.
До появления стандарта ES-2015 в циклах for/of
или спред-операторах массивов были довольно неудобные emit
’ы. Поэтому в TypeScript используется более простой дефолтный emit
, который поддерживает только массивы. Также он поддерживает итерацию других типов данных с флагом --downlevelIteration
. При использовании этого флага код получается более точным, но менее лаконичным.
Параметр --downlevelIteration
в новой версии исключён, так как большинство пользователей пользуются итерацией по массивам. Тем не менее emit
, который поддерживает только массивы, работает по-другому в некоторых пограничных случаях.
Обратите внимание на пример ниже.
[...Array(5)]
Он эквивалентен массиву из следующего примера.
[undefined, undefined, undefined, undefined, undefined]
Однако TypeScript трансформирует его в такой код.
Array(5).slice();
В то же время Array(5)
создаёт массив с длинной 5, но без определённых элементов.
1 in [undefined, undefined, undefined] // true
1 in Array(3) // false
При использовании метода slice()
TypeScript создаёт массив, в котором значения не определены, но у которого есть индексы.
Этот нюанс может показаться несущественным, но он доставляет неудобства специалистам. Поэтому в TypeScript 3.6 представлен новый инструмент __spreadArrays
. Подробную информацию о нём можно найти в соответствующем пулреквесте.
Промисы — самый распространённый способ работы с асинхронными данными. Однако использование API, ориентированных на промисы, доставляет разработчикам неудобства. В TypeScript 3.6 улучшена работа с неправильно обрабатываемыми промисами.
Например, специалисты иногда забывают использовать .then()
или await
перед передачей промисов в другую функцию. Новая версия TypeScript сообщает об этой ошибке и предлагает пользователю использовать await
.
interface User {
name: string;
age: number;
location: string;
}
declare function getUserData(): Promise;
declare function displayUser(user: User): void;
async function f() {
displayUser(getUserData());
// ~~~~~~~~~~~~~
// Argument of type 'Promise' is not assignable to parameter of type 'User'.
// ...
// Did you forget to use 'await'?
}
Также разработчики иногда пытаются получить доступ к методу до использования await
или .then()
. Пример ниже показывает, как TypeScript 3.6 реагирует на эту ошибку.
async function getCuteAnimals() {
fetch("https://reddit.com/r/aww.json")
.json()
// ~~~~
// Property 'json' does not exist on type 'Promise'.
//
// Did you forget to use 'await'?
}
То есть даже если пользователь не использует await
, он получает дополнительную информацию, которая позволяет исправить ошибку.
Также появилась опция быстрого исправления описанной выше ошибки (см. иллюстрацию).
Подробности смотрите в соответствующем issue.
В релиз кандидате TypeScript 3.6 улучшена поддержка юникода в идентификаторах. Пример ниже.
const 𝓱𝓮𝓵𝓵𝓸 = "world"; // ранее не поддерживалось, теперь можно использовать с '--target es2015'
import.meta
в System.jsВ TypeScript 3.6 включена поддержка трансформации import.meta
в context.meta
. Нагляднее в коде.
// Этот модуль:
console.log(import.meta.url)
// трансформируется в:
System.register([], function (exports, context) {
return {
setters: [],
execute: function () {
console.log(context.meta.url);
}
};
});
get
и set
можно использовать в окружающем контекстеВ более ранних версиях методы доступа get
и set
нельзя было использовать в окружающем контексте (например, в declare class
). Теперь разработчики могут пользоваться геттерами и сеттерами.
declare class Foo {
// Возможно начиная с версии 3.6+.
get x(): number;
set x(val: number): void;
}
В ранних версиях TypeScript попытка объединить классы и функции приводила к ошибке в любом случае. В предварительной версии TypeScript 3.6 классы и функции с модификатором declare
можно объединять.
Это позволяет использовать такой код.
export declare function Point2D(x: number, y: number): Point2D;
export declare class Point2D {
x: number;
y: number;
constructor(x: number, y: number);
}
Раньше пришлось бы писать так.
export interface Point2D {
x: number;
y: number;
}
export declare var Point2D: {
(x: number, y: number): Point2D;
new (x: number, y: number): Point2D;
}
Благодаря этому можно использовать вызываемый конструктор, что обеспечивает объединение пространства имён. Подробности смотрите в оригинальном пулреквексте.
--build
и --incremental
Начиная с версии 3.0 в TypeScript включена поддержка --build
, а в версии 3.4 добавлена поддержка --incremental
. Эти флаги позволяют гибко структурировать проекты и ускоряют разработку. К сожалению, --build
и --incremental
не работали со сторонними инструментами, например, Gulp или Webpack. В TypeScript 3.6 данная возможность появилась.
Чтобы создавать сборки с --incremental
, разработчики могут использовать API createIncrementalProgram
и createIncrementalCompilerHost
. Новая функция readBuilderProgram
позволяет использовать файлы из .tsbuildinfo
, сгенерированные указанными API.
Подробности смотрите в пулреквесте.
Редакторы и среды разработки типа Visual Studio Code и Visual Studio автоматически предлагают исправления и дополнения. Это реализовано с помощью TypeScript. Старые версии TypeScript автоматически добавляли точку с запятой в конце каждого выражения. Это неудобно для части пользователей, так как в некоторых стайлгайдах использование точки с запятой не предустматривается.
Новая версия TypeScript интеллектуально решает проблему точек с запятыми. Если в вашем проекте они используются, редактор их добавляет в код. В противном случае точки с запятыми не добавляются.
Детали в пулреквесте.
Старые версии TypeSript по умолчанию используют для автоимпорта синтаксис ECMAScript. Это неприемлемо для части проектов TypeScript с определёнными настройками компилятора, а также для проектов Node с использованием нативного JavaScript, где используется require
.
TypeScript 3.6 интеллектуально решает проблему автоимпортов. Для этого он изучает существующие импорты, которые уже используются в проекте.
Подробнее в пулреквесте.
constructor
становятся конструкторомМетоды с названием constructor
считаются конструктором независимо от того, как они объявляются: с помощью идентификатора или строкового имени.
class C {
"constructor"() {
console.log("Я конструктор.");
}
}
Важное исключение — вычисляемые значения остаются методами.
class D {
["constructor"]() {
console.log("Я метод, а не конструктор!");
}
}
Ниже несколько важных изменений.
window
изменился с Window
на Window & typeof globalThis
.GlobalFetch
используется WindowOrWorkerGlobalScope
.experimental-webgl
используется webgl
или webgl2
.В проектах JavaScript TypeScript обращается к комментариям JSDoc, чтобы определить объявленный типы.
/**
* @param {string} arg
*/
/**
* oh, hi, were you trying to type something?
*/
function whoWritesFunctionsLikeThis(arg) {
// 'arg' has type 'any'
}
В релиз кандидате вводится запрет на использование избегающих последовательностей в ключевых словах.
while (true) {
\u0063ontinue;
// ~~~~~~~~~~~~~
// error! Keywords cannot contain escape characters.
}
Команда TypeScript ждёт обратную связь по релиз кандидату TypeScript 3.6. Полученные данные помогут выпустить официальную версию TypeScript.
Адаптированный перевод статьи Announcing TypeScript 3.6 RC. Мнение администрации «Хекслета» может не совпадать с мнением автора оригинальной публикации.