11 июля майкрософт разместили в своем блоге запись о выходе в бету второй версии их компилятора TypeScript, а 30 августа появилась запись о RC. Эти события не могут не радовать, т.к. [email protected] несет с собой множество новых фич, а так же обновленные привычные механизмы. Данная статья является переводом аккумулированной информации о TypeScript 2.0 собранной с официальных источников (список находится в конце статьи). За подробностями добро пожаловать под кат, а, чтобы сразу опробовать прочитанное, устанавливайте новую версию:

npm install -g [email protected]

Содержание

  1. 'Null' и 'undefined' типы
  2. Анализ типа на основании контроля потока
  3. Распознавание составных типов
  4. Больше типов литералов
  5. Тип 'never'
  6. Свойства и индексы доступные только для чтения
  7. Указание типа 'this' для функций
  8. Поддержка паттерна глобального поиска в 'tsconfig.json'
  9. Улучшенный поиск модулей: BaseUrl, сопоставление путей, rootDirs и трассировка
  10. Сокращенная декларация внешних модулей
  11. Звездочка (*) в именах подключаемых модулей
  12. Поддержка определения универсальных модулей UMD
  13. Необязательные свойства и методы класса
  14. Модификаторы доступа 'private' и 'protected' для конструктора
  15. Абстрактные свойства и методы доступа
  16. Неявные сигнатуры индексов в литералах объектов
  17. Подключение встроенных деклараций опцией '--lib'
  18. Опции '--noUnusedParameters' и '--noUnusedLocals' для уведомления о неиспользуемых объявлениях
  19. Поиск модулей с расширением '.js'
  20. Поддержка "target: es5" при "module: es6"
  21. Завершающая запятая в аргументах и параметрах функций
  22. Новая опция '--skipLibCheck'
  23. Разрешены дублирующие объявления между файлами деклараций
  24. Новая опция '--declarationDir'
  25. Файлы деклараций внешних модулей
  26. Используемые в статье материалы

Null и undefined типы (Null- and undefined-aware types)

В TypeScript есть два специальных типа, Null и Undefined, чьими значения являются null и undefined соответственно. Раньше отсутствовала возможность явно дать имена этим типам, но теперь null и undefined могут использоваться, как типы, независимо от режима проверки типов.

Ранее проверка типов считала, что null и undefined могут быть присвоены любому типу. Фактически, null и undefined были допустимыми значениями для любого типа и было невозможно намеренно исключить их (и как следствие невозможно обнаружить ошибочное их использование).

Флаг --strictNullChecks

Флаг --strictNullChecks позволяет переключить компилятор в новый режим строгой проверки нулевого типа.

В режиме строгой проверки нулевого типа значения null и undefined более не являются подтипами составных типов и могут быть значениями только самих себя и any (с одним исключением, когда undefined так же может быть присвоен к void). Поэтому, несмотря на то, что T и T | undefined считаются синонимами в обычном режиме проверки типов (т.к. undefined является подтипом для любого T), они становятся разными типами в строгом режиме проверки типов и только T | undefined разрешает undefined значения. Эти же правила истины для пары T  и T | null.

Пример

// Скомпилировано с флагом --strictNullChecks
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1;  // Ок
y = 1;  // Ок
z = 1;  // Ок
x = undefined;  // Ошибка
y = undefined;  // Ок
z = undefined;  // Ок
x = null;  // Ошибка
y = null;  // Ошибка
z = null;  // Ок
x = y;  // Ошибка
x = z;  // Ошибка
y = x;  // Ок
y = z;  // Ошибка
z = x;  // Ок
z = y;  // Ок

Проверка присвоения перед использованием (Assigned-before-use checking)

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

Пример

// Скомпилировано с флагом --strictNullChecks
let x: number;
let y: number | null;
let z: number | undefined;
x;  // Ошибка, ссылке не предшествует присвоение
y;  // Ошибка, ссылке не предшествует присвоение
z;  // Ок
x = 1;
y = null;
x;  // Ок
y;  // Ок

Компилятор проверяет, что переменным явно присвоены значения, выполняя анализ типов на основании контроля потока. Далее по тексту мы обсудим детали этой темы.

Необязательные аргументы и свойства

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

// Скомпилировано с флагом --strictNullChecks
type T1 = (x?: number) => string;              // У `x` тип `number | undefined`
type T2 = (x?: number | undefined) => string;  // У `x` тип `number | undefined`

Предохранители не-null и не-undefined типа (Non-null and non-undefined type guards)

Прежде, доступ к свойству или вызов функции генерировали ошибку на этапе компиляции, если тип объекта или функции включали null или undefined. Однако, предохранители типа были расширены и теперь совершают не-null и не-undefined проверки.

Пример

// Скомпилировано с флагом --strictNullChecks
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
    f(x);  // Ок, у `x` тип `number`
}
else {
    f(x);  // Ошибка, у `x` тип `number?` (фактически `number | null | undefined`)
}
let a = x != null ? f(x) : "";  // У `a` тип `string`
let b = x && f(x);  // У `b` тип `string`? (фактически `string | null | undefined`)

// Помните, что к логичскому И (&&) применяется принцип "короткого цикла вычислений". 
// В данном случае, если `x` имеет значение отличное от `null` и `undefined`, то будет произведено 
// вычисление выражения `f(x)`, результат которого будет типа `string`.
// Если же `x` типа `null` или `undefined`, то `b` примет значение `x`.
// Таким образом получается, что `x` может быть типа `string` или `null | undefined`, что можно
// сократить как `string?`

Благодаря предохранителям не-null и не-undefined типа для сравнения с null или undefined можно использовать операторы ==, !=, === или !==, например: x != null или x === undefined. Влияние на определение типов переменных явно выражается JavaScript семантикой (например, оператор двойного равенства совершает проверку на оба значения, неазвисимо от того, какое указано (null и undefined), в то время, как оператор тройного равенства совершает провеку только на указанное значение).

Предохранители типов для "свойств через точку" (Dotted names in type guards)

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

Пример

interface Options {
    location?: {
        x?: number;
        y?: number;
    };
}

function foo(options?: Options) {
    if (options && options.location && options.location.x) {
        const x = options.location.x;  // Компилятор знает, что у `x` тип `number`
    }
}

Предохранители типов для "свойств через точку" так же работают с пользовательскими функциями проверок типа, а так же с операторами typeof и instanceof и не зависят от флага компилятора --strictNullChecks.

От пер.: Следующий текст я не знаю, как перевести более-менее корректно, поэтому оставляю оригинал: "A type guard for a dotted name has no effect following an assignment to any part of the dotted name. For example, a type guard for x.y.z will have no effect following an assignment to x, x.y, or x.y.z".

Операторы выражений

Операторы выражений позволяют операндам включить в тип null и/или undefined, но результат выражения всегда будет не-null и не-undefined типа (от пер.: не очень понимаю логики данного изменения. Возможно, имеется ввиду, что переменная намеренно будет приводится к 0 или "" если ее тип null или undefined и она учавствует в каком-то выражении. Например, как в примере ниже: если a и b придут как null, то результат их суммы будет 0, т.е. number. Это лишь мои предположения, своими - делитесь в комментариях).

// Скомпилировано с флагом --strictNullChecks
function sum(a: number | null, b: number | null) {
    return a + b;  // Произведет значение типа `number`
}

Оператор && добавляет null и/или undefined к типу правого операнда, в зависимости от того, каким из этих типов представлен левый операнд, а оператор || убирает и null и undefined типы у левого операнда в результате объединения.

// Скомпилировано с флагом --strictNullChecks
interface Entity {
    name: string;
}
let x: Entity | null;
let s = x && x.name;  // У `s` тип `string | null` (`null` присутствует у левого операнда)
let y = x || { name: "test" };  // У `y` тип `Entity` (`x` лишается типа `null`)


Расширение типа

Типы null и undefined не могут быть расширены типом any в режиме строгой проверки типов.

let z = null;  // У `z` тип `null`

В режиме обычной проверки типов подразумеваемым типом z является any из-за расширения типов, но в режиме строгой проверки подразумеваемым типом z является null (как следствие null является единственным возможным значением для z).

Оператор не-null утверждения

Новый ! оператор, который следует после выражения, может быть использован, чтобы подсказать компилятору, что тип операнда не-null и не-undefined в контексте, когда компилятор сам не может принять этот факт. В частности, результатом операции x! будет значение типа с исключенными null и undefined. Так же, как и в случае приведения типов через синтаксис <T>x и x as T, оператор не-null утверждения ! просто удаляется из скомпилированного JavaScript кода.

// Скомпилировано с флагом --strictNullChecks
function validateEntity(e: Entity?) {
    // Бросить исключение, если у `e` значение `null` или недопустимый `Entity`
}

function processEntity(e: Entity?) {
    validateEntity(e);
    let s = e!.name;  // Утверждаем, что на данном этапе у `e` не-null значение и есть доступ к свойству `name`. Иначе было бы брошено исключение и до этой строки выполнение скрипта бы не дошло
}

Совместимость

Новые возможности спроектированы таким образом, что могут быть использованы в обоих (обычном и строгом) режимах проверки типов. В частности, типы null и undefined автоматически вырезаются из объединения типов в обычном режиме проверки типов (потому что являются подтипами всех других типов), а оператор не-null утверждения ! разрешен, но не оказывает никакого влияния на принятия решения компилятором. Таким образом обновленные для использования в строгом режиме файлы объявлений (declaration files) все еще могут быть использованы в обычном режиме проверки типов для обратной совместимости.

На практике режим строгой проверки типов требует, чтобы все файлы были осведомлены о наличии null и undefined типов.

 

Анализ типа на основании контроля потока (Control flow based type analysis)


TypeScript 2.0 реализует анализ типа на основании контроля потока для локальных переменных и аргументов функций. Ранее анализ типа произведенный на основании предохранителей типа был ограничен if и ?: условными выражениями и не брал во внимание последствия присвоения значений, а так же такие котролирующие поток конструкции, как return и break. С выходом TypeScript 2.0, проверка типов анализирует все возможные потоки, проходящие через утверждения и выражения для того, чтобы сделать более точные выводы о вероятном (суженном) типе переменной или аргумента функции, который является составным типом, в любом месте кода.

Пример

function foo(x: string | number | boolean) { // У `x` составной тип `string | number | boolean`
    if (typeof x === "string") {
        x; // Тут у `x` тип `string` (сузился)
        x = 1;
        x; // Тут уже у `x` тип `number` (сузился)
    }
    x; // А тут у `x` тип `number | boolean`, т.к. тип `string` отрабатывает выше и преобразуется в `number`. Если же верхний `if` не отработал, то тип `x` точно не `string` (сузился)
}

function bar(x: string | number) { // У `x` составной тип `string | number`
    if (typeof x === "number") {
        return;
    }
    x; // У `x` тип `string` (сузился)
}

Анализ типа на основании контроля потока особенно полезен при включенном режиме --strictNullChecks, т.к. нулевые типы представляются в качестве составных:

function test(x: string | null) {
    if (x === null) {
        return;
    }
    x; // Тут и далее по телу функции тип `x` может быть только `string`
}

Кроме того в режиме строгой проверки типов --strictNullChecks, анализ типа на основании контроля потока так же предупреждает о необходимости явного определения значения у переменных, чей тип не разрешает undefined значений.

function mumble(check: boolean) {
    let x: number; // Тип не позволяет `undefined` значения
    x; // Ошибка, `x` - `undefined`
    if (check) {
        x = 1;
        x; // Ок, значение присвоено
    }
    x; // Ошибка, `x` вероятно `undefined` (если `if` выше не отработал)
    x = 2;
    x; // Ок, значение точно присвоено
}
 

Распознавание составных типов (Tagged union types)

TypeScript 2.0 реализует поддержку распознавания составных типов. В частности теперь компилятор TS поддерживает предохранители типов, которые научились сужать составные типы на основании тестирования распознаваемого свойства и более того расширяет эту возможность при использовании оператора switch (от пер.: на деле все проще, чем на словах).

Пример

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

// В TypeScript 1.8 вы бы делали так
function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
            // Преобразовать из 'Shape' в 'Circle'
            let c = shape as Circle;
            return Math.PI * c.radius ** 2;

        case "square":
            // Преобразовать из 'Shape' в 'Square'
            let sq = shape as Square;
            return sq.sideLength ** 2;
    }
}

// TypeScript 2.0 позволяет писать код чище
function area(s: Shape) {
    // В данном `switch`-е тип `s` сужается в каждом условии `case`
    // в соответствии со значением распознанного свойства (`kind`), позволяя осуществить доступ к другим свойствам
    // распознанного объекта не прибегая к утверждению типов через условия или оператор `as`.
    switch (s.kind) {
        case "square": return s.size * s.size; // Тут `s` распознался как `Square`
        case "rectangle": return s.width * s.height; // Тут `s` распознался как `Rectangle`
        case "circle": return Math.PI * s.radius * s.radius; // Тут `s` распознался как `Circle`
    }
}

function test1(s: Shape) {
    if (s.kind === "square") {
        s;  // `Square`
    }
    else {
        s;  // `Rectangle | Circle`
    }
}

function test2(s: Shape) {
    if (s.kind === "square" || s.kind === "rectangle") {
        return;
    }
    s;  // `Circle`
}

С помощью распознавания составных типов становится проще сохранять строгую типизацию используя `switch` в `JavaScript`. Например, библиотеки вроде Redux будут часто использовать этот паттерн, обрабатывая действия (actions).

 

Больше типов литералов (More Literal Types)


Строковые типы литералов появились в версии 1.8 и оказались чрезвычайно полезными. Как видно из раздела выше, нам удалось использовать их, чтобы реализовать распознавание составных типов.

Мы хотели, чтобы типы отличные от string так же получили эту возможность. В 2.0 каждый уникальный boolean, number и enum получит свой тип!

type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
let nums: Digit[] = [1, 2, 4, 8];

// Ошибка! '16' не входит в тип 'Digit'!
nums.push(16);

Используя состаные типы мы можем выражать некоторые сущности более натурально.

interface Success<T> {
    success: true;
    value: T;
}

interface Failure {
    success: false;
    reason: string;
}

type Result<T> = Success<T> | Failure;

В данном примере тип Result<T> выражает что-то, что может привести к ошибке. Если это что-то пройдет успешно, то у результата будет значение, если же произойдет ошибка, то результат будет содержать причину. К свойству value можно обратиться только в случае успеха.

declare function tryGetNumUsers(): Result<number>;

let result = tryGetNumUsers();
if (result.success === true) {
    // Тут у 'result' тип 'Success<number>'
    console.log(`Сервер вернуд ${result.value} пользователей`);
}
else {
    // Тут у 'result' тип 'Failure'
    console.error("Произошла ошибка при запросе пользователей!", result.reason);
}

Свойства с типом enum так же могут иметь свой тип.

enum ActionType { Append, Erase }

interface AppendAction { 
    type: ActionType.Append;
    text: string;
}

interface EraseAction {
    type: ActionType.Erase;
    numChars: number;
}

function updateText(currentText: string, action: AppendAction | EraseAction) {
    if (action.type === ActionType.Append) {
        // Тип 'action' распознался как 'AppendAction'
        return currentText + action.text;
    }
    else {
        // Тип 'action' распознался как 'EraseAction'
        return currentText.slice(0, -action.numChars);
    }
}
 

Тип never


TypeScript 2.0 вводит новый примитивный тип never. Тип never представляет тип значений, которые никогда не произойдут. В частности тип never представляет функции, чей возвращаемый тип данных не определен, а так же переменные, для которых предохранители типов никогда не выполняются.

Тип never имеет следующие характеристики:

  • Это подтип, который может быть присвоен любому типу.
  • Отсутствие типа является подтипом для never, а так же может быть присвоено к never (за исключением самого never).
  • Если для функционального выражения или стрелочной функции явно не указан тип возвращаемого значения и отсутствуют выражения return или все return возвращают тип never и если точка выхода из функции не доступна (так определил контроль усправления потока), то прогнозируемый тип возвращаемого значения функции будет never.
  • В функциональном выражении, для которого явно указан never в качестве типа возвращаемого значения все return-ы (если они есть) должны возвращать выражения типа never, а так же точка выхода из функции не должна быть достижима.

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

Несколько примеров функций, возвращаемых never:

// Функция, возвращаемая `never` не должна иметь точки выхода
function error(message: string): never {
    throw new Error(message);
}

// Предполагаемый возвращаемый тип `never`
function fail() {
    return error("Something failed");
}

// Функция, возвращаемая `never` не должна иметь точки выхода
function infiniteLoop(): never {
    while (true) {
    }
}

Несколько примеров использования функций, возвращаемых never:

// Предполагаемый возвращаемый тип `number`
function move1(direction: "up" | "down") {
    switch (direction) {
        case "up":
            return 1;
        case "down":
            return -1; 
    }
    return error("Это никогда не выполнится");
}

// Предполагаемый возвращаемый тип `number`
function move2(direction: "up" | "down") {
    return direction === "up" ? 1 :
        direction === "down" ? -1 :
        error("Это никогда не выполнится");
}

// Предполагаемый возвращаемый тип `T`
function check<T>(x: T | undefined) {
    return x || error("Undefined value");
}

Из-за того, что never можно присвоить к любому типу функция, которая возвращает never может быть использована, когда она ожидает в качестве аргумента callback-функцию, возвращающую более конкретный тип:

function test(cb: () => string) { // Ожидается, что `cb` вернет `string` или `never`
    let s = cb();
    return s;
}

test(() => "hello"); // callback возвращает `string`
test(() => fail()); // По идее компилятор начнет ругаться на ф-ию, которая не определена до того, как проверит возвращаемый ею тип
test(() => { throw new Error(); }) // callback возвращает `never`

От пер.: складывается ощущение, что тип never добавили как замену undefined для более точной работы компилятора в тех местах, где понятие  undefined интерпретировалось двояко. К тому же, с появлением флага --strictNullChecks и выносом null и undefined как самостоятельные типы появилась потребность как-то отслеживать неопределенный результат.

 

Свойства и индексы доступные только для чтения (Read-only properties and index signatures)


Для свойств и индексов теперь можно указывать модификатор доступа readonly, что позволяет только читать данное свойство или индекс.

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

Кроме того, в некоторых ситуациях readonly подразумевается контекстом:

  • Для свойства определен только метод доступа get, но не set.
  • Свойства объекта типа enum считаются readonly свойствами.
  • Свойства экспортируемых из модуля объектов, объявленных через const считаются readonly свойствами.
  • Импортируемые (через import) из модулей сущности (от пер.: классы, переменные, константы и т.д.) считаются readonly.
  • Обращение к сущностям импортируемым из модулей, спроектированных в стиле ES2015 считаются readonly (например foo.x доступен только для чтения, если foo объявлен как import * as foo from "foo").

Примеры

interface Point {
    readonly x: number;
    readonly y: number;
}

var p1: Point = { x: 10, y: 20 };
p1.x = 5;  // Ошибка, `p1.x` доступен только для чтения

var p2 = { x: 1, y: 1 };
var p3: Point = p2;  // Ок, доступная только для чтения ссылка на `p2`
p3.x = 5;  // Ошибка, `p3.x` доступен только для чтения
p2.x = 5;  // Ок, но также изменится `p3.x`, т.к. это ссылка
class Foo {
    readonly a = 1;
    readonly b: string;
    constructor() {
        this.b = "hello";  // Присваивание разрешено в конструкторе, где `foo` был определен
    }
}
let a: Array<number> = [0, 1, 2, 3, 4];
let b: ReadonlyArray<number> = a;
b[5] = 5;      // Ошибка, элементы доступны только для чтения
b.push(5);     // Ошибка, метод `push` недоступен (т.к. он модифицирует массив)
b.length = 3;  // Ошибка, `length` только для чтения
a = b;         // Ошибка, отсутствуют методы модификаций (от пер.: скорее всего имеется ввиду, что в интерфейсе `Array` методы могут модифицировать массив, а в интерфейсе `ReadOnlyArray` судя по-всему все методы доступны только для чтения
 

Указание типа this внутри функций (Specifying the type of this for functions)


Теперь в функциях и методах можно указывать, какой тип this они ожидают.

По умолчанию тип this в функции - any. Начиная с TypeScript 2.0, вы можете явно указать this в качестве аргумента. this является ложным аргументом, который идет первым в списке аргументов функции:

function f(this: void) {
    // Убедитесь, что `this` не используется в этой функции
}

this parameters in callbacks

Библиотеки тоже могут использовать this аргумент, чтобы показать, как callback-функция будет вызвана.

Пример

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void означает, что addClickListener ожидает, что аргумент onclick будет функцией без какого-либо контекста.

Вот что теперь произойдет, если вы вызовете эту функцию, передав в качестве аргумента callback-функцию с отличным аргументом this:

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // Упс! Использование этой callback-функции приведет к ошибке в `runtime`-е
        this.info = e.message;
    };
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // Вот тут произойдет ошибка, т.к. `addClickListener` ожидает, что `this` его аргумента будет `void`

--noImplicitThis

Новый флаг позволяет оповещать об использовании функций, где тип this не указан.

 

Поддержка паттерна глобального поиска в tsconfig.json (Glob support in tsconfig.json)


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

Данный паттерн поддерживается для двух свойств "include" и "exclude".

Пример

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "outFile": "../../built/local/tsc.js",
        "sourceMap": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ]
}

Подддерживаемые глобальным паттерном маски:

  • * соотстветствует нуля и более символам (за исключением разделителей каталогов)
  • ? соответствует одному любому символу (за исключением разделителей каталогов)
  • **/ рекурсивно сооветствует любой поддиректории

Если сегмент глобального паттерна содержит только * или .*, то только поддерживаемые расширения файлов будут включены (по умолчанию это .ts, .tsx, и .d.ts, а также .js и .jsx если allowJs опция установлена в true).

Если значения опций "files" и "include" не указаны, то по умолчанию компилятор включит все TypeScript файлы (.ts, .d.ts и .tsx) из текущей папки и ее подпапках за исключением тех, которые указаны в опции "exclude". JS файлы (.js and .jsx) также будут включены, если опция allowJs установена в true.

Если значения опций "files" или "include" указаны, то компилятор включит объединения файлов, указанных в этих двух опциях. Файлы в папке, указанной в опции "outDir" всегда исключены, пока они явно не будут указаны опцией "files" (даже, при наличии опции "exclude").

Файлы, включенные опцией "include" могут быть отфильтрованы опцией "exclude". Однако, файлы указанные явно опцией "files" всегда включены, независимо от того, указаны ли они в опции "exclude". По умолчанию, если опция "exclude" не указана, будут исключены папки node_modules, bower_components и jspm_packages.

 

Улучшенный поиск модулей: BaseUrl, сопоставление путей, rootDirs и трассировка (Module resolution enhancements: BaseUrl, Path mapping, rootDirs and tracing)


TypeScript 2.0 представляет дополнительный набор инструментов для оповещения компилятора о том, где искать файлы деклараций конкретных модулей.

Для более подробной информации смотри документацию по Module Resolution.

Base URL

Использование опции baseUrl обычная практика в приложениях, используемых загрузчик модулей AMD, где модули копируются в отдельную папку во время выполнения. При импорте поиск модулей с абсолютными путями будет происходить относительно baseUrl.

Пример

{
  "compilerOptions": {
    "baseUrl": "./modules"
  }
}

Теперь поиски модуля "moduleA" будут производиться в ./modules/moduleA

import A from "moduleA";

Сопоставление путей

Иногда модули находятся за пределами baseUrl. Загрузчики используют настройку сопоставления имен, чтобы связать имя модуля с его расположением, смотри документации по RequireJs и SystemJS.

Компилятор TypeScript поддерживает объявление таких сопоставлений, указанных в опции "paths" в файле tsconfig.json.

Пример

Например, импорты модуля "jquery" будут транслированы в "node_modules/jquery/dist/jquery.slim.min.js" во время выполнения.

{
  "compilerOptions": {
    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery.slim.min.js"]
    }
}

Использование "path" так же позволяет реализовать и более сложные сопоставления, включая запасные пути. Рассмотрим конфигурацию проекта, где часть модулей доступны в одном месте, а остальные - в другом.

Включение виртуальных папок опцией rootDirs

Используя "rootDirs" можно сообщить компилятору о нескольких корневых папок, образующих виртуальную структуру; таким образом, поиск модулей с относительными путями будет производиться внутри этой виртуальной структуры.

Пример

Имеется следующая структура проекта:

src
 └── views
     └── view1.ts (Содержит импорт './template1')
     └── view2.ts

 generated
 └── templates
         └── views
             └── template1.ts (Содержит импорт './view2')

На шаге сборки все файлы из папки /src/views и /generated/templates/views будут скопированы в одну папку. Во время выполнения view может быть уверен, что необходимый ему template находится рядом и доступен для импорта с использованием относительного пути "./template".

Опция "rootDirs" указывает список корневых папок, чье содержимое будет ожидаемо объединено во время выполнения. Продолжая пример выше файл tsconfig.json должен выглядеть так:

{
  "compilerOptions": {
    "rootDirs": [
      "src/views",
      "generated/templates/views"
    ]
  }
}

Трассировка поиска модулей

Опция --traceResolution предлагает удобный способ увидеть, как компилятор осуществил поиск модулей.

tsc --traceResolution
 

Сокращенная декларация внешних модулей (Shorthand ambient module declarations)


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

declarations.d.ts
declare module "hot-new-module";

Все импортируемые из "сокращенного" модуля сущности будут иметь тип any.

import x, {y} from "hot-new-module";
x(y);
 

"Звездочка" в именах подключаемых модулей (Wildcard character in module names)


Импорт сторонних ресурсов с использованием загрузчиков модулей (напр. AMD или SystemJS) никогда не был простым; раньше внешние модули необходимо было объявлять для каждого ресурса.

TypeScript 2.0 поддерживает использование "звездочки" (*) для объявления "семейства" модулей; т.о. декларация необходима один раз для группы, а не для каждого ресурса в отдельности.

Пример

declare module "*!text" {
    const content: string;
    export default content;
}
// Или наоборот
declare module "json!*" {
    const value: any;
    export default value;
}

Теперь можно импортировать модули, соответствующие "*!text" или "json!*".

import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);

"Имена модулей через звездочку" могут быть еще полезнее, при переходе с нетипизированной базы кода. В сочетании с сокращенной декларацией внешних модулей целый набор модулей может быть легко объявлен как any.

Пример

declare module "myLibrary/*";

Любой импортированный из myLibrary модуль будет расценен компилятором как any; т.о. заглушив любые проверки используемых типов сущностей этих модулей.

import { readFile } from "myLibrary/fileSystem/readFile`;

readFile(); // readFile is 'any'
 

Поддержка определения универсальных модулей UMD (Support for UMD module definitions)


Некоторые библиотеки спроектированы так, чтобы их можно было использовать с различными загрузчиками модулей или и вовсе без них (модули, доступные через глобальные переменные). Они известны как UMD или Isomorphic модули. Получить доступ к таким библиотекам можно как через импорт, так и через глобальную переменную. Например:

math-lib.d.ts
export const isPrime(x: number): boolean;
export as namespace mathLib;

Теперь получить доступк к библиотеке может через импорт модуля:

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // Ошибка: нельзя обратиться через глобальную переменную внутри модуля

А так же к библиотеке можно обратиться через глобальную переменную внутри скрипта (скрипт это JS файл без импортов и экспортов)

mathLib.isPrime(2);
 

Необязательные свойства и методы класса


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

Пример

class Bar {
    a: number;
    b?: number;
    f() {
        return 1;
    }
    g?(): number;  // Тело необязательного метода может быть опущено
    h?() {
        return 2;
    }
}

Если скомпилировать с флагом --strictNullChecks,  к типам необязательных свойств и методов автоматически добавится undefined. Таким образом свойство b выше имеет тип number | undefined, а метод g - (() => number) | undefined. Предохранители типов могут использоваться для отсечения undefined части:

function test(x: Bar) {
    x.a;  // number
    x.b;  // number | undefined
    x.f;  // () => number
    x.g;  // (() => number) | undefined
    let f1 = x.f();            // number
    let g1 = x.g && x.g();     // number | undefined
    let g2 = x.g ? x.g() : 0;  // number
}
 

Модификаторы доступа private и protected для конструктора (Private and Protected Constructors)


Конструктор класса может быть помечен как private или protected. Класс с private конструктором не может быть проинициализирован за пределами класса, а так же не может быть расширен. Класс с protected конструктором тоже не может быть проинициализирован, однако может быть расширен.

Пример

class Singleton {
    private static instance: Singleton;

    private constructor() { }

    static getInstance() {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    } 
}

let e = new Singleton(); // Ошибка: у 'Singleton' приватный конструктор.
let v = Singleton.getInstance();
 

Абстрактные свойства и методы доступа (Abstract properties and accessors)


Внутри абстрактного класса могут быть объявлены абстрактные свойства и/или методы доступа. Любой наследуемый класс обязан реализовать абстрактные свойства или тоже должен быть абстрактным. Абстрактные свойства не могут быть проинициализированы. Абстрактные методы доступа не могут иметь тело.

Example

abstract class Base {
    abstract name: string;
    abstract get value();
    abstract set value(v: number);
}

class Derived extends Base {
    name = "derived";

    value = 1;
}
 

Неявные сигнатуры индексов в литералах объектов (Implicit index signatures)


Литерал объекта теперь может быть присвоен типу с сигнатурой индекса в том случае, если все известные свойства этого объекта сопоставимы с сигнатурой индекса. Это позволяет передать переменную-объект в качестве аргумента функции, которая ожидает словарь или массив:

function httpService(path: string, headers: { [x: string]: string }) { }

const headers = {
    "Content-Type": "application/x-www-form-urlencoded"
};

httpService("", { "Content-Type": "application/x-www-form-urlencoded" });  // Ок
httpService("", headers);  // Раньше это вызвало бы ошибку компилятора, но сейчас - нет
 

Подключение встроенных деклараций опцией --lib (Including built-in type declarations with --lib)


Указание компилятору на использование ES6/ES2015 в вашем проекте было ограничено опцией target: ES6. Встречайте, --lib; с помощью этой опции вы можете указать список встроенных скгруппированных по API деклараций, которые должны быть включены в ваш проект. Например, если вы хотите во время выполнения поддержку Map, Set и Promise (представим самый современный браузер), просто перечислите их: --lib es2015.collection,es2015.promise. Схожим образом вы можете исключить декларации, которые не хотите включать в проект, например DOM, когда вы работаете в окружении node: --lib es5,es6.

Список доступных сгруппированных по API деклараций:

  • dom
  • webworker
  • es5
  • es6 / es2015
  • es2015.core
  • es2015.collection
  • es2015.iterable
  • es2015.promise
  • es2015.proxy
  • es2015.reflect
  • es2015.generator
  • es2015.symbol
  • es2015.symbol.wellknown
  • es2016
  • es2016.array.include
  • es2017
  • es2017.object
  • es2017.sharedmemory
  • scripthost

Примеры

tsc --target es5 --lib es5,es2015.promise
{
    "compilerOptions": {
        "lib": ["es5", "es2015.promise"]
    }
}
 

Опции --noUnusedParameters и --noUnusedLocals для уведомления о неиспользуемых объявлениях (Flag unused declarations with --noUnusedParameters and --noUnusedLocals)


Для компилятора TypeScript 2.0 появились новые опции, которые помогают поддерживать код чистым. --noUnusedParameters отмечает как ошибку любой объявленный, но неиспользуемый аргумент функции или метода. --noUnusedLocals помечает ошибкой любые локальные (не-экспортируемые) объявления, такие как переменные, функции, классы, импорты и т.д. Неиспользуемые приватные члены классы тоже будут помечены ошибкой при включенной опции --noUnusedLocals.

Пример

import B, { readFile } from "./b";
//     ^ Ошибка: `B` объявлен, но не используется
readFile();


export function write(message: string, args: string[]) {
    //                                 ^^^^  Ошибка: 'arg' объявлен, но не используется
    console.log(message);
}

Аргументы функции, начинающиеся с _ не являются ошибкой при такой проверке, например:

function returnNull(_a) { // Ок
    return null;
}

Поиск модулей с расширением .js (Module identifiers allow for .js extension)


До выхода TypeScript 2.0 названия модулей подразумевались как "написанные без расширения"; например, когда компилятор видел запись import as import d from "./moduleA.js", то он искал определение модуля "moduleA.js" в файлах ./moduleA.js.ts или ./moduleA.js.d.ts. Такое поведения затрудняло использование сборщиков\загрузчиков как SystemJS, который ожидает относительный путь в определении модуля.

Начиная с TypeScript 2.0 компилятор будет искать определение модуля "moduleA.js" в ./moduleA.ts или ./moduleA.d.ts.

Поддержка "target: es5" при "module: es6" (Support "target: es5" with "module: es6")


Комбинация, которая ранее считалась недопустимой, теперь разрешена! Это должно облегчить использование основанных на ES2015 шейкеров как rollup.

 

Завершающая запятая в аргументах и параметрах функций (Trailing commas in function parameter and argument lists)


Завершающая запятая в аргументах и параметрах функции теперь разрешена. Эта возможность является реализацией предложения Stage-4 (утверждено и будет реализовано в будущей версии ES) ECMAScript, которое совместимо с предыдущими версиями: ES3/ES5/ES6.

Пример

function foo(
  bar: Bar, 
  baz: Baz, // завершающая запятая в списке параметров - Ок
) {
  // ...
}

foo(
  bar,
  baz, // и в аргументах функции тоже ок
);

Новая опция --skipLibCheck (New --skipLibCheck)


В компилятор TypeScript 2.0 добавлена новая опция --skipLibCheck, которая позволяет пропустить проверку типов внутри файлов деклараций (файлы с расширением .d.ts). Когда в программу включены большие файлы деклараций, компилятор тратит много времени на проверки типов там, где по задумке ошибок быть не должно. Таким образом, можно значительно увеличить время сборки, не выполняя проверку таких файлов.

Так как декларации одних файлов могут задействовать другие файлы (напр., через import), то часть ошибок может быть не отловлена при включенной опции --skipLibCheck. Например, если в недекларационном файле происходит дополнение типа, объявляенного в файле декларации, то ошибку можно получить только после проверки этого декларационного файла. На деле такие ситуации случаются очень редко.

 

Разрешены дублирующие объявления между файлами деклараций (Allow duplicate identifiers across declarations)


Подобные ситуации были основным источником появления ошибки дублирующего определения: несколько файлов деклараций объявляли одни и те же члены интерфейсов.

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

Однако внутри одного файла дублирования по-прежнему запрещены.

Пример

// file1.d.ts
interface Error {
    stack?: string;
}

// file2.d.ts
interface Error {
    code?: string;
    path?: string;
    stack?: string;  // Ок
}

Новая опция --declarationDir (New --declarationDir)


Опция --declarationDir позволяет указать, в какую папку будут генерироваться файлы деклараций, чтобы держать их отдельно от JavaScript файлов.

Файлы деклараций внешних модулей

Файлы деклараций это основной способ использования API внешних библиотек в TypeScript, но их получение и расположение всегда оставалось областью для размышления и улучшения. Используя TypeScript 2.0 файлы внешних деклараций можно подключать не выходя за пределы использования npm. В качестве примера, чтобы установить файлы деклараций для библиотеки lodash достаточно всего лишь выполнить npm команду:

npm install --save @types/lodash

На данном этапе вы уже можете использовать lodash в вашем TypeScript коде без лишних телодвижений.

import * as _ from "lodash";
_.padStart("Hello TypeScript!", 20, " ");

Или, если вы не используете модули, то можете просто использовать глобальную переменную _ (установленные через npm декларации являются UMD совместимыми):

_.padStart("Hello TypeScript!", 20, " ");

Список доступных файлов деклараций можно найти на странице профиля @types или воспользовавшись графическим веб интерфейсом. На самом деле все эти пакеты являются копией пакетов из репозитория definitely typed. Поэтому, чтобы добавить новые файлы деклараций, необходимо запушить их сперва в репозиторий definitely typed и ожидать появления на npm. В будущем будет добавлен расширенный способ описания файлов деклараций для авторов модулей. Такие инструменты как Typings и tsd по-прежнему работают, и мы в свою очередь будем работать бок о бок с их сообществом для обеспечения более плавного перехода.

 

Используемые в статье материалы


What's new in TypeScript

Announcing TypeScript 2.0 Beta

Announcing TypeScript 2.0 RC

The Future of Declaration Files