TypeScript 3.0! Да, он вышел, и в нем по-настоящему много нововведений. Под катом вы найдете подробное описание всех новинок последней версии, среди которых режим build, новый тип unknown, значительные изменения в API, улучшения производительности и многое другое. Присоединяйтесь!
Вышел TypeScript 3.0! Началась новая веха на пути разработки языка TypeScript, помощника всех пользователей JavaScript.
Если вы еще не знакомы с языком TypeScript, не поздно узнать о нем сейчас! TypeScript представляет собой расширение JavaScript, разработанное для использования в современном варианте этого языка статических типов. Компилятор TypeScript читает код на языке TypeScript, содержащий, в частности, объявления и аннотации типов, и выдает чистый, легко читаемый код JavaScript, в котором эти конструкции преобразованы и удалены. Полученный код запускается в любой среде выполнения, соответствующей стандарту ECMAScript, например в вашем любимом браузере или на серверной платформе Node.js.
Использование подобной среды означает, что код будет проанализирован на предмет наличия ошибок или опечаток, прежде чем будет запущен пользователями, но его преимущества этим не ограничиваются. Благодаря всей этой информации и результатам анализа TimeScript повышает удобство работы, предоставляя возможность автоматического завершения кода и такие средства навигации, как Find all References (Найти все ссылки), Go to Definition (Перейти к определению) и Rename (Переименовать), в вашем любимом редакторе.
Чтобы начать работу с языком и получить дополнительную информацию, перейдите по ссылке. Если вы хотите попробовать TypeScript 3.0 прямо сейчас, можете скачать его из NuGet или через npm, введя команду
npm install -g typescript
Кроме того, доступна поддержка в следующих редакторах:
- Visual Studio 2017 (версия 15.2 и более поздние);
- Visual Studio 2015 (требуется обновление 3);
- Visual Studio Code (нужно установить предварительный выпуск, пока эта возможность не поддерживается в главном);
- Sublime Text 3 на сайте PackageControl.
Другие редакторы обновляются согласно собственному графику, но в скором времени все они будут иметь отличную поддержку TypeScript.
Обзор версии 3.0
После выхода TypeScript 2.0 мы сделали краткий обзор вклада предшествующих версий в его нынешнее состояние. Между выпусками TypeScript 1.0 и 2.0 в язык вошли типы объединений, условия типов (type guards), поддержка современного стандарта ECMAScript, псевдонимы типов, поддержка JSX, литеральные и полиморфные типы this
. Если сюда же включить привнесенные TypeScript 2.0 типы, не допускающие значение «null», анализ потока управления, поддержку размеченных объединений (tagged unions), типы this и упрощенную модель получения файлов .d.ts
, то можно сказать, что этот период полностью определил основы работы TypeScript.
Итак, что было сделано с тех пор? Что привело нас к TypeScript 3.0, помимо новых возможностей стандарта ECMAScript вроде асинхронных функций async
/await
, генераторов и оператора расширения/остатка (rest/spread)?
TypeScript 2.1 стал фундаментальным выпуском, в котором была представлена статическая модель метапрограммирования в JavaScript. Запрос ключа (keyof
), индексный доступ (T[K]
) и типы сопоставленных объектов ({ [K in keyof T]: } T[K]}
) — вот список инструментов, которые использовались для более эффективного моделирования библиотек React, Ember, Lodash и других.
В выпусках TypeScript 2.2 и 2.3 появилась поддержка шаблонов классов mixin, тип object
(представляет объект, не являющийся примитивом) и значения по умолчанию для универсальных типов. Эти возможности использовались в ряде проектов, например в Angular Material и Polymer. Кроме того, в TypeScript 2.3 была реализована возможность детального управления типами this
, позволяющая языку хорошо работать с такими библиотеками, как Vue, и добавлен флаг checkJs
, позволяющий проверять типы в файлах JavaScript.
В выпусках TypeScript 2.4 и 2.6 продолжается история о повышении строгости проверки типов функций, связанная с некоторыми из самых давних отзывов о нашей системе типов. Был введен флаг --strictFunctionTypes
, принудительно устанавливающий контравариантность параметров. В выпуске 2.7 тенденция к строгости сохранилась и выразилась в проверке в классах с помощью флага --strictPropertyInitialization
.
В TypeScript 2.8 вводятся условные типы, мощный инструмент статического выражения решений на основе типов, а в выпуске 2.9 обобщается оператор keyof и упрощается импорт для типов.
И это приводит нас к TypeScript 3.0! Несмотря на новое целое число в номере, в выпуске 3.0 изменилось немногое (что предполагает очень легкое обновление). В нем представлен новый гибкий и масштабируемый способ структурирования проектов, мощная новая поддержка работы со списками параметров, новые типы для обеспечения явных проверок, улучшенная поддержка JSX, существенно более удобная для пользователя диагностика ошибок и многое другое.
Что нового?
- Ссылки на проекты
- Режим
--build
- Управление структурой вывода
- Планы на будущее
- Режим
- Извлечение и распространение списков параметров с помощью кортежей
- Новые возможности кортежных типов
- Тип
unknown
- Улучшенная диагностика ошибок и среда пользователя
- Связанные диапазоны ошибок
- Улучшенная диагностика и обработка ошибок
- Поддержка свойства
defaultProps
в JSX - Директивы
/// <reference lib="..." />
- Повышение скорости работы в редакторе
- Рефакторинг именованных операторов импорта
- Завершение конечных тегов и рамки с контуром
- Быстрые исправления для недостижимого кода и неиспользуемых меток
- Критические изменения
unknown
является зарезервированным именем типа- Критические изменения в API
Ссылки на проекты
Довольно часто для сборки библиотеки или приложения нужно выполнить несколько шагов. Допустим, ваша база кода содержит каталоги src
и test
. Предположим, у вас имеется папка client
, где хранится код клиентской части приложения, и папка server, содержащая код серверной части на платформе Node.js, и каждый из них заимствует часть кода из папки shared
. Возможно, вы используете так называемый «единый репозиторий» и имеете множество проектов, которые находятся в сложной зависимости друг от друга.
Одна из самых главных функций, над которыми мы работали при выпуске TypeScript 3.0, получила название «ссылки на проект», и она призвана упрощать работу с подобными сценариями.
Благодаря ссылкам на проект одни проекты на языке TypeScript могут зависеть от других. В частности, файлам tsconfig.json
разрешено ссылаться на другие файлы tsconfig.json
. Задание этих зависимостей упрощает разделение кода на более мелкие проекты, поскольку компилятор TypeScript (и его инструментарий) получают возможность понять порядок сборки и структуру выходных данных. Это означает, что сборка происходит быстрее и выполняется инкрементно (поэтапно), поддерживаются прозрачная навигация, редактирование и рефакторинг по различным проектам. Поскольку TypeScript 3.0 закладывает основу проекта и предоставляет API, любой инструмент сборки должен быть в состоянии это обеспечить.
Как это выглядит?
В качестве простого примера здесь приводится файл tsconfig.json
, содержащий ссылки на проекты.
// ./src/bar/tsconfig.json
{
"compilerOptions": {
// Needed for project references.
"composite": true,
"declaration": true,
// Other options...
"outDir": "../../lib/bar",
"strict": true, "module": "esnext", "moduleResolution": "node",
},
"references": [
{ "path": "../foo" }
]
}
В нем есть два новых поля: composite
и references
.
Поле references
просто указывает на другие файлы tsconfig.json
(или папки, в которых они содержатся). Каждая ссылка здесь представляет собой просто объект с полем path
(«путь») и указывает компилятору TypeScript, что для сборки данного проекта требуется сначала собрать другой проект, на который он ссылается.
По-видимому, такую же важность имеет поле composite
. Поле composite
гарантирует, что будут включены определенные параметры, позволяющие любому проекту, зависящему от данного, ссылаться на него и включать его в себя при инкрементной сборке. Важное значение имеет возможность интеллектуальной и инкрементной сборки, поскольку одной из главных причин, по которой вы можете отказаться от проекта, является скорость сборки.
Например, если проект front-end
зависит от проекта shared
, а shared
— от core, то наши API, касающиеся ссылок на проекты, помогут определить изменения в core, но вновь собрать только shared, если изменились типы, произведенные проектом core
(т. е. файлы .d.ts
). Это значит, что изменение в core не влечет за собой глобальную повторную сборку всех проектов. По этой причине установка флага composite
порождает также установку и флага declaration
.
Режим --build
В TypeScript 3.0 появится набор API для ссылок на проекты, чтобы и другие инструменты могли обеспечить этот быстрый инкрементный способ сборки. В частности, в подключаемом модуле gulp-typescript эти API уже используются! Таким образом, позднее ссылки на проекты будут интегрированы с выбранными вами оркестраторами сборки.
Однако для многих простых приложений и библиотек желательно не использовать внешние инструменты. Вот почему в команде tsc теперь устанавливается новый флаг --build
.
Команда tsc --build
(или ее псевдоним, tsc -b
) берет набор проектов и выполняет их сборку, а также сборку зависимых проектов. При использовании нового режима сборки, во-первых, должен быть установлен флаг --build
, и он может сочетаться с некоторыми другими флагами:
--verbose
: показывает каждый этап, требуемый процессом сборки.--dry
: выполняет сборку, не порождая выходных файлов (полезно в сочетании с параметром--verbose
).–clean
: пытается удалить выходные файлы, соответствующие заданным входным.--force
: принудительно выполняет полную, неинкрементную сборку проекта.
Управление структурой вывода
Одно тонкое, но невероятно полезное преимущество ссылок на проекты состоит в логической способности сопоставлять входные файлы соответствующим выходным.
Если вы когда-либо пробовали разделить клиентскую и серверную части приложения, то могли столкнуться с проблемами управления выходной структурой.
Например, если оба файла client/index.ts и server/index.ts ссылаются на shared/index.ts для следующих проектов:
… то при попытке собрать проекты client и server мы получим…
…а не…
Заметьте, что после сборки мы получили копии папки shared как в client, так и в server. Мы потратили лишнее время на двукратную сборку shared и добавили нежелательный уровень вложенности в lib / client / client и lib / server / server.
Проблема состоит в том, что TypeScript с жадностью ищет файлы .ts и пытается включить их в данную компиляцию. В идеале TypeScript должен был понять, что эти файлы не должны участвовать в сборке в одной и той же компиляции, и вместо этого обратиться к файлам .d.ts за информацией о типах.
Создание файла tsconfig.json для shared приводит именно к этому результату. Он сигнализирует компилятору TypeScript:
- что проект shared должен собираться независимо
- и что при импорте из ../shared мы должны искать файлы .d.ts в его выходном каталоге.
Это позволяет избежать запуска двукратной сборки, а также случайного включения всего содержимого shared.
Планы на будущее
Чтобы глубже разобраться с проектными ссылками и возможностями их использования, прочтите о них более подробно в трекере данного выпуска. В ближайшее время мы подготовим документацию по ссылкам на проекты и режиму build.
Мы стремимся, чтобы авторы других инструментов программирования могли поддерживать ссылки на проекты и продолжали улучшать среду редактирования в том, что касается этой функции. Мы намерены добиться того, чтобы работа со ссылками на проекты шла так же гладко, как и разработка кода с одним единственным файлом tsconfig.json. Если вы в конечном итоге начнете использовать ссылки на проекты, мы будем благодарны за любые отзывы.
Извлечение и распространение списков параметров с помощью кортежей
Мы часто принимаем это как должное, но JavaScript позволяет нам считать списки параметров значениями первого класса — с помощью либо arguments, либо параметров типа rest (например, ...rest).
function call(fn, ...args) {
return fn(...args);
}
Заметьте, что call работает для функций с любым числом параметров. В отличие от других языков, JavaScript не заставляет нас определять call0, call1, call2 и т. д. следующим образом:
function call0(fn) {
return fn();
}
function call1(fn, param1) {
return fn(param1);
}
function call2(fn, param1, param2) {
return fn(param1, param2);
}
function call3(fn, param1, param2, param3) {
return fn(param1, param2, param3);
}
К сожалению, в течение некоторого времени не было хорошего способа выразить это в языке TypeScript без объявления конечного числа перегрузок:
// TODO (billg): 5 overloads should *probably* be enough for anybody?
function call<T1, T2, T3, T4, R>(fn: (param1: T1, param2: T2, param3: T3, param4: T4) => R, param1: T1, param2: T2, param3: T3, param4: T4): R
function call<T1, T2, T3, R>(fn: (param1: T1, param2: T2, param3: T3) => R, param1: T1, param2: T2, param3: T3): R
function call<T1, T2, R>(fn: (param1: T1, param2: T2) => R, param1: T1, param2: T2): R
function call<T1, R>(fn: (param1: T1) => R, param1: T1): R;
function call<R>(fn: () => R, param1: T1): R;
function call(fn: (...args: any[]) => any, ...args: any[]) {
return fn(...args);
}
Уф! Еще один смертельный случай с тысячей перегрузок! Или, по крайней мере, стольких перегрузок, сколько потребуется пользователям.
TypeScript 3.0 позволяет лучше моделировать подобные сценарии, поскольку теперь параметры вида rest могут быть универсальными, и их тип определяется как кортежный. Вместо того чтобы объявлять каждую из этих перегрузок, мы говорим, что rest-параметр ...args из функции fn должен быть параметром-типом, который расширяет массив, и затем повторно используем это для параметра...args, который передает функция call:
function call<TS extends any[], R>(fn: (...args: TS) => R, ...args: TS): R {
return fn(...args);
}
Когда мы вызываем функцию call, TypeScript пытается извлечь список параметров из того, что мы передаем в fn, и превратить это в кортеж:
function foo(x: number, y: string): string {
return (x + y).toLowerCase();
}
// The `TS` type parameter is inferred as `[number, string]`
call(foo, 100, "hello");
Когда TypeScript определяет TS как [число, строка], и мы завершаем повторное использование TS на rest-параметре функции call, экземпляр функции выглядит следующим образом:
function call(fn: (...args: [number, string]) => string, ...args: [number, string]): string
А в TypeScript 3.0 при использовании кортежа в rest параметр сворачивается в оставшуюся часть списка параметров! Приведенный выше экземпляр сводится к простым параметрам без кортежей:
function call(fn: (arg1: number, arg2: string) => string, arg1: number, arg2: string): string
Итак, в дополнение к вылавливанию ошибок преобразования типа при передаче неправильных аргументов:
function call<TS extends any[], R>(fn: (...args: TS) => R, ...args: TS): R {
return fn(...args);
}
call((x: number, y: string) => y, "hello", "world");
// ~~~~~~~
// Error! `string` isn't assignable to `number`!
…и определению типа из других аргументов:
call((x, y) => { /* .... */ }, "hello", 100);
// ^ ^
// `x` and `y` have their types inferred as `string` and `number` respectively.
…мы также можем увидеть кортежные типы, которые эти функции определяют извне:
function tuple<TS extends any[]>(...xs: TS): TS {
return xs;
}
let x = tuple(1, 2, "hello"); // has type `[number, number, string]`
Но обратите внимание на один нюанс. Чтобы проделать всю эту работу, нам пришлось расширить возможности кортежей…
Новые возможности кортежных типов
Чтобы можно было моделировать список параметров в виде кортежа (как мы только что обсуждали), нам пришлось немного переосмыслить кортежные типы. До выпуска TypeScript 3.0 лучшим из того, что разрешалось моделировать с помощью кортежей, были порядок и длина набора параметров.
Однако списки параметров — это не просто упорядоченные списки типов. Например, параметры в конце могут быть необязательными:
// Both `y` and `z` are optional here.
function foo(x: boolean, y = 100, z?: string) {
// ...
}
foo(true);
foo(true, undefined, "hello");
foo(true, 200);
Последний параметр может быть rest-параметром.
// `rest` accepts any number of strings - even none!
function foo(...rest: string[]) {
// ...
}
foo();
foo("hello");
foo("hello", "world");
И наконец, есть одно довольно интересное свойство списков параметров — они могут быть пустыми:
// Accepts no parameters.
function foo() {
// ...
}
foo();
Поэтому, чтобы кортежи соответствовали спискам параметров, нам нужно было смоделировать каждый из этих сценариев.
Во-первых, теперь в конце кортежи могут находиться необязательные элементы:
/**
* 2D, or potentially 3D, coordinate.
*/
type Coordinate = [number, number, number?];
Тип Coordinate создает кортеж с необязательным свойством с именем 2 — элемент с индексом 2 может и не быть определенным! Интересно, что, поскольку кортежи используют числовой литеральный типы для своего свойства length (длина), свойство length кортежа Coodinate имеет тип 2 | 3.
Во-вторых, в конце кортежа теперь может присутствовать элемент rest.
type OneNumberAndSomeStrings = [number, ...string[]];
Благодаря элементам rest кортежи демонстрируют очень интересное поведение «неограниченности с конца». В приведенном выше примере типа OneNumberAndSomeStrings требуется, чтобы тип его первого свойства был number, и допускается одно или более свойств типа string. Индексирование этого типа кортежа произвольным числом number возвращает тип string | number, поскольку значение индекса неизвестно. Аналогично, поскольку длина кортежа неизвестна, значение свойства length — просто number.
Следует отметить, что в отсутствие других элементов элемент rest в кортеже идентичен самому себе:
type Foo = [...number[]]; // Equivalent to `number[]`.
Наконец, кортежи теперь могут быть пустыми! Хотя это не слишком полезно при использовании вне списков параметров, пустой тип кортежа можно определить как []:
type EmptyTuple = [];
Как и можно было ожидать, пустой кортеж имеет свойство length, равное 0, а индексация числом number возвращает тип never.
Улучшенная диагностика ошибок и среда пользователя
С течением времени мы получаем все больше и больше запросов от членов нашего сообщества относительно улучшения сообщений об ошибках. Хотя эта работа далека от завершения, мы вас услышали и сделали в выпуске TypeScript 3.0 ряд улучшений.
Связанные диапазоны ошибок
Отчасти цель хорошего сообщения об ошибке состоит в том, чтобы указать пользователю также и способ ее исправления или, в первую очередь, дать понять, почему это сообщение появилось. В большинстве случаев в нем содержится много информации или указывается несколько причин его появления. Из анализа этих причин мы можем заключить, что ошибки проистекают из разных частей кода.
Связанные диапазоны ошибок — это новый способ предоставления данной информации пользователям. В TypeScript 3.0 сообщения об ошибках могут порождать сообщения в других местах кода, чтобы пользователи могли выяснить причины и следствия ошибки.
В некотором смысле связанные сообщения об ошибках могут не просто дать пользователю объяснение, но и указать путь к месту, где все пошло не так.
Эти интервалы появятся также в режиме терминала при выполнении команды tsc с включенным режимом --pretty, хотя мы все еще занимаемся улучшением пользовательского интерфейса и учтем ваши отзывы!
Улучшенная диагностика и обработка ошибок
При подготовке выпуска TypeScript 2.9 мы начали уделять больше внимания сообщениям об ошибках, а в выпуске 3.0 мы действительно попытались решить основные задачи, которые позволили бы выполнить интеллектуальную, ясную и точную диагностику ошибки. Сюда относятся, в частности, подбор соответствующих типов при несоответствиях в типах объединения и выход непосредственно на источник ошибки для определенных типов сообщений.
Мы считаем, что наши усилия оправдались, и в итоге вы получите более короткие и ясные сообщения об ошибках.
Тип unknown
Тип any (любой) — это тип в TypeScript, подходящий к чему угодно. Поскольку он охватывает типы всех возможных значений, то не заставляет нас делать какие-либо проверки, прежде чем мы попытаемся эти значения вызывать, конструировать или получать доступ к их свойствам. Он также позволяет присвоить значения типа any переменным, которые ожидают значений любого другого типа.
Эта возможность в целом полезна, но не может обеспечить достаточную строгость.
let foo: any = 10;
// All of these will throw errors, but TypeScript
// won't complain since `foo` has the type `any`.
foo.x.prop;
foo.y.prop;
foo.z.prop;
foo();
new foo();
upperCase(foo);
foo `hello world!`;
function upperCase(x: string) {
return x.toUpperCase();
}
Иногда в TypeScript хочется описать тип, не подходящий ни к чему. Это бывает полезно для API, который хочет просигнализировать: «здесь может быть любое значение, поэтому вы должны произвести некоторую проверку перед тем, как его использовать». И пользователи вынуждены анализировать возвращаемые значения в целях безопасности.
В TypeScript 3.0 вводится новый тип с названием unknown, который делает именно это. Подобно типу any, типу unknown присваивается любое значение, однако, в отличие от any, тип unknown не может быть присвоен почти никакому другому без утверждения типа. Вы не можете получать доступ к объектам типа unknown, а также вызывать их или конструировать.
Если в приведенном выше примере подставить unknown вместо any, все случаи использования объекта foo приведут к ошибке:
let foo: unknown = 10;
// Since `foo` has type `unknown`, TypeScript
// errors on each of these locations.
foo.x.prop;
foo.y.prop;
foo.z.prop;
foo();
new foo();
upperCase(foo);
foo `hello world!`;
function upperCase(x: string) {
return x.toUpperCase();
}
Вместо этого мы вынуждены либо выполнять проверку, либо использовать утверждение типа и убедить систему проверки типов в том, что мы лучше знаем, что надо делать.
let foo: unknown = 10;
function hasXYZ(obj: any): obj is { x: any, y: any, z: any } {
return !!obj &&
typeof obj === "object" &&
"x" in obj && "y" in obj && "z" in obj
}
// Using a user-defined type guard...
if (hasXYZ(foo)) {
// ...we're allowed to access certain properties again.
foo.x.prop;
foo.y.prop;
foo.z.prop;
}
// We can also just convince TypeScript we know what we're doing
// by using a type assertion.
upperCase(foo as string);
function upperCase(x: string) {
return x.toUpperCase();
}
Заметьте: если вы, чтобы достичь аналогичного поведения, используете такой тип, как {} | null | undefined, то тип unknown в конструкциях вроде условных типов обычно ведет себя более желаемым образом, поскольку условные типы распространяются на типы объединения:
type Arrayify<T> = T extends any ? Array<T> : never;
type A = Arrayify<{} | null | undefined>; // null[] | undefined[] | {}[]
type B = Arrayify<unknown>; // unknown[]
Поддержка defaultProps в JSX
Обратите внимание: файлы .d.ts библиотеки React в период их написания, возможно, еще не поддерживали этой функциональности.
Если вы когда-либо использовали в современном языке TypeScript/JavaScript инициализаторы по умолчанию, то должны знать, как удобны они бывают при написании операторов вызовов функций. Они дают нам полезные синтаксические средства вызова функций более легким способом, позволяющим не указывать конкретные аргументы. При этом авторы функций получают возможность убедиться, что значения аргументов всегда четко определены.
function loudlyGreet(name = "world") {
// Thanks to the default initializer, `name` will always have type `string` internally.
// We don't have to check for `undefined` here.
console.log("HELLO", name.toUpperCase());
}
// Externally, `name` is optional, and we can potentially pass `undefined` or omit it entirely.
loudlyGreet();
loudlyGreet(undefined);
В библиотеке React существует аналогичная концепция для компонентов и их свойств (props). При создании нового элемента с использованием компонента React ищет свойство, называемое defaultProps, для заполнения опущенных значений для props.
// Some non-TypeScript JSX file
import * as React from "react";
import * as ReactDOM from "react-dom";
export class Greet extends React.Component {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
}
static defaultProps = {
name: "world",
};
}
// Notice no `name` attribute was specified!
// vvvvvvvvv
const result = ReactDOM.renderToString(<Greet />);
console.log(result);
Обратите внимание, что в <Greet /> не указано значение name. Когда создается элемент Greet, константа name будет инициализирована значением «world», и этот код напечатает: Hello world!.
К сожалению, TypeScript не понимал, что defaultProps имеет какое-либо отношение к вызовам JSX. Вместо этого пользователи часто были вынуждены объявлять свойства необязательными и использовать ненулевые утверждения внутри функции render:
export interface Props { name?: string }
export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
// Notice the `!` ------v
return <div>Hello ${name!.toUpperCase()}!</div>;
}
static defaultProps = { name: "world"}
}
Или применять некие изощренные утверждения типа, чтобы исправить тип компонента перед его экспортом.
Вот почему язык TypeScript 3.0 поддерживает новый псевдоним типа в пространстве имен JSX, называемом LibraryManagedAttributes. Несмотря на длинное имя, это всего лишь вспомогательный тип, который сообщает TypeScript, какие атрибуты принимает тег JSX. Короче говоря, используя этот общий тип, мы можем моделировать определенное поведение React для defaultProps и, в некоторой степени, для propTypes.
export interface Props {
name: string
}
export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
}
static defaultProps = { name: "world"}
}
// Type-checks! No type assertions needed!
let el = <Greet />
Имейте в виду, что существуют ограничения. Для defaultProps, которые явно указывают свой тип как нечто вроде Partial , или компонентов-функций без состояния (stateless function components, SFC), для которых defaultProps объявлены как Partial , все свойства станут необязательными. В качестве обходного пути можно полностью исключить аннотацию типа для defaultProps как компонента класса (см. пример выше) или использовать инициализаторы по умолчанию для SFC по стандарту ES2015:
function Greet({ name = "world" }: Props) {
return <div>Hello ${name.toUpperCase()}!</div>;
}
И последнее, что следует отметить. Хотя поддержка и встроена в TypeScript, текущие файлы .d.ts в репозитории DefinitelyTyped в настоящее время не используют ее, поэтому возможно, что @types/react пока не содержит доступных изменений. В настоящее время мы ожидаем стабилизации по всему сообществу DefinitelyTyped, чтобы гарантировать минимальное количество сбоев при описанных изменениях.
Директивы /// <reference lib="..." />
Одна из проблем, которые мы наблюдаем в сообществе, состоит в том, что полизаполнители (polyfills) — библиотеки, которые предоставляют более новые API в старых средах выполнения, часто имеют собственные файлы объявления (файлы .d.ts), которые пытаются сами дать определения для этих API. Иногда это хорошо, однако данные объявления являются глобальными и могут создавать проблемы в сочетании со встроенными в TypeScript файлами lib.d.ts в зависимости от параметров компилятора пользователя, таких как --lib и --target. Например, объявления для библиотеки core-js могут конфликтовать со встроенным файлом объявлений lib.es2015.d.ts.
Для решения этой проблемы TypeScript 3.0 предлагает, чтобы в файлах объявлялись встроенные API, присутствие которых ожидается, путем использования новой ссылочной директивы: /// <reference lib="..." />.
Например, полизаполнитель для объекта Promise стандарта ES2015 теперь может просто содержать строки
/// <reference lib="es2015.promise" />
export {};
При наличии такого комментария, даже если потребитель TypeScript 3.0 явно использовал целевой объект, который не вносит файл определений lib.es2015.promise.d.ts, импорт указанной библиотеки гарантирует, что Promise присутствует.
Повышение скорости работы в редакторе
Тем, кто еще не знаком с языком, среда TypeScript предоставляет службы, облегчающие написание кода благодаря использованию ее синтаксических и семантических знаний. Эта служба действует как встроенный обработчик для TypeScript и JavaScript под управлением редакторов, таких как Visual Studio, Visual Studio Code и любой другой редактор с подключаемым модулем TypeScript. Она предоставляет возможности, которые очень нравятся пользователям, в частности завершение кода, функцию Go to Definition («переход к определению») и даже быстрые исправления и рефакторинг кода. TypeScript 3.0 продолжает поддерживать эти службы.
Рефакторинг именованных операторов импорта
Иногда квалификация импорта каждого объекта именем модуля, из которого он пришел, загромождает код.
import * as dependency from "./dependency";
// look at all this repetition!
dependency.foo();
dependency.bar();
dependency.baz();
С другой стороны, если мы по отдельности импортируем используемые объекты, то можем обнаружить, что после их многократных применений новому читателю кода становится непонятно, откуда они импортируются.
import { foo, bar, baz } from "./dependency";
// way lower in the file...
foo();
bar();
baz();
Независимо от того, какой способ будет выбран вами сейчас, вы сможете изменить свое решение позднее. TypeScript 3.0 предоставляет возможность рефакторинга, так что подобное переключение не вызовет сложностей.
Завершение конечных тегов и рамки с контуром
В настоящее время TypeScript предоставляет две новые возможности, повышающие удобство работы с тегами JSX:
- завершение закрывающих тегов JSX;
- генерирование рамок со сворачиваемым контуром для JSX.
Быстрые исправления для недостижимого кода и неиспользуемых меток
TypeScript теперь позволяет быстро исправлять код — удалять недоступный код, а также неиспользуемые метки.
Критические изменения
Вы всегда можете следить за предстоящими критическими изменениями в языке, а также в наших API.
Мы надеемся, что в TypeScript 3 очень мало критических изменений, влияющих на работу приложений. Изменения в языке должны быть минимально деструктивными, и большинство критических изменений в наших API ориентированы на удаление устаревших функций.
unknown — зарезервированное имя типа
Поскольку unknown — новый встроенный тип, это слово теперь нельзя использовать в объявлениях типов, таких как интерфейсы, псевдонимы типов или классы.
Критические изменения в API
- Устаревший внутренний метод LanguageService#getSourceFile был удален, поскольку получал неодобрительные отзывы за два последних года. См. #24540.
- Устаревшая функция TypeChecker#getSymbolDisplayBuilder и связанные с ней интерфейсы были удалены. См. #25331. Вместо них следует использовать emitter (генератор событий) и node builder.
- Устаревшие функции escapeIdentifier и unescapeIdentifier были удалены. Поскольку изменился принцип работы API идентификатора в целом, они служили функциями идентификации всего в нескольких выпусках. Если вам нужно, чтобы ваш код вел себя как раньше, достаточно просто удалить вызовы этих функций. В качестве альтернативы следует использовать безопасные с точки зрения типов escapeLeadingUnderscores и unescapeLeadingUnderscores, если типы указывают на то, что они требуются (поскольку они используются для преобразования в «фирменные» типы __String и string или из них).
- Методы TypeChecker#getSuggestionForNonexistentProperty, TypeChecker#getSuggestionForNonexistentSymbol и TypeChecker#getSuggestionForNonexistentModule сделаны внутренними, они больше не входят в состав нашего общедоступного API. См. #25520.
Перспективы
Своим большим успехом TypeScript обязан сообществу. Мы в долгу перед теми, кто внес свой вклад в работу над компилятором, языковой службой, репозиторием DefinitelyTyped и интеграцией инструментов, для которой использовались любые комбинации из вышеперечисленного. Мы также благодарны нашим пользователям, которые постоянно делились полезными для нас отзывами и подталкивали нас к улучшениям.
В будущем мы планируем уделять больше внимания системе типов и инструментам, совершенствовать ссылки на проекты и всемерно расширять доступность TypeScript (как языка, так и проекта). Плюс ко всему мы хотели бы выяснить, что можно сделать, чтобы предоставить больше возможностей авторам и пользователям инструментария в сообществе JavaScript. Мы хотели бы помочь тем разработчикам, которые могут с пользой применять TypeScript, даже если не обращаются к нему напрямую.
Следите за нашей картой по мере того, как наши идеи претворяются в жизнь, не стесняйтесь написать пару строк, чтобы дать нам обратную связь, в формате комментария, в Twitter или в виде заявки о проблеме. Мы всегда стараемся работать лучше.
Всем, кто до сих пор сопровождал нас в путешествии по TypeScript, спасибо! Мы планируем создать для вас максимально удобную рабочую среду. Что касается всех остальных, мы надеемся, что вы начнете изучать и полюбите TypeScript так же сильно, как мы.
Удачи в разработке!
Команда TypeScript
Автор: Александр Гуреев