Обзор TypeScript 3.0

30.08.2018 Майкрософт представила новую версую TypeScript 

Если вы еще не знакомы с TypeScript, то стоит обратить на него внимание. TypeScript это надстройка над JavaScript, основная ее цель: привнести статическую типизацию в современный JS. Компилятор TypeScript сканирует код в котором можно определить или аннотировать типы, и в случае неправильного использования типа он генерирует ошибки, в чистом JS, ошибка могла появиться только на этапе выполнения. Также TSC преобразует ваш TypeScript код в JS код необходимого вам ECMAScript стандарта, который может выполнятся как в браузере так и на бекенде(Node.js). TypeScript отлично подходит для больших приложений, так как статический анализ код становится намного проще.

Начать работать с TypeScript очень просто, установите его через npm:

npm install -g typescript

Список нововведений TypeScript 3.0

Ссылки на проекты (Project references)

Очень распространенной практикой является иметь несколько этапов сборки вашего проекта. Например ваше приложение может иметь две папки src и test, или вы храните код фронт-енда в папке client и код бэкенда в папке server, а общий код для хранится в папке shared. Такие зависимости довольно сложно поддерживать и чтобы это решить в TypeScript 3.0 были введены проектные ссылки

Project references позволяют TypeScript проектам зависеть от других TypeScript проектов, такие зависимости нужно определять в tsconfig.json файле, где нужно указать путь к tsconfig.json файлу зависимости. Это позволит разбить вашу монолитную кодовую базу на несколько проектов, TypeScript сам определит очередность компиляции и структуру скомпилированного кода. Это делает возможным процесс инкрементальной компиляции, что приведет к уменьшению времени сборки проекта

Пример использование проектных ссылок

// ./src/client/tsconfig.json
{
    "compilerOptions": {
        "composite": true,
        "declaration": true,
        "outDir": "../../lib/client",
        "strict": true, "module": "esnext", "moduleResolution": "node",
    },
    "references": [
        { "path": "../server" }
    ]
}

Было добавлено два новых параметра composite и references

В references нужно указывать tsconfig.json или папки которые их содержат. Каждая ссылка сейчас объект с одним свойством path, она говорит TSC что компиляция данного проекта требует компиляции указанного проекта.

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

Режим —build

TypeScript 3.0 предоставляет набор API для ссылки на проекты, что бы другие средства поддерживали инкрементальное поведение. Например gulp-typescript уже использует эти возможности. Хотя,для большинства маленьких проектов нет надобности подключать сторонние утилиты. Именно для этого в tsc был добавлен флаг —build (алиас -b). Команда tsc -b собирает принимает несколько проектов и собирает их вместе с зависимостями. Флаг -b должен задан первым, далее можно указать опциональные параметры:

  • —verbose: показывает каждый шаг что требуется для сборки
  • —dry: собирает проект, без генерирования файлов
  • —clean: делает попытку удалить ранее скомпилированные файлы
  • —force: делает принудительную не инкрементальную сборку проекта

Извлечение и распределение списка аргументов функции с помощью кортежей

JavaScript позволяет нам воспринимать аргументы функции как объекты первого класса (first-class values) — благодаря использование ключевого слова arguments или остаточных параметров (…rest)

function call(fn, ...args) {
    return fn(...args);
}

Функцию call можно вызвать с любым количеством параметров, в отличии от других языков JavaScript не требует перегрузки функций, с разным количеством параметров. Но к сожалению, до этого момента не было хорошего способа статически определить несколько возможных вызовов в TypeScript. К счастью TypeScript 3.0 позволяет делать rest параметры обобщенными. Вместо определения каждой возможной перегрузки функции, ми говорим что остаточный параметр …args функции fn должен наследовать класс array.

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();
}
// Парметр `TS` определен как `[number, string]`
call(foo, 100, "Hello owrld");

Расширенный типы кортежей

Теперь кортежи позволяют указывать необязательные элементы

code language=»javascript»] /** * 2D или 3D координаты . */ type Coordinate = [number, number, number?]; [/code]

Тип Coordinate создает кортеж который может иметь три или два элемента.

Также кортежи теперь могут иметь остаточные параметры

type OneNumberAndSomeStrings = [number, …string[]];
Последним нововведением с кортежами есть возможности задавать пустые кортежи.

type EmptyTuple = [];

Новый тип — unknown

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

let bar: any = 10;
bar.x.prop;
bar();
new bar();

Бывают случаи когда нужно использовать менее универсальный тип в TypeScript. Это полезно если вам нужно показать то что результат функции может быть значением, и что клиентскому коду нужно сделать кое-какие проверки перед его использованием В TypeScript 3.0 был введен новый тип unknown. Переменной тип unknown можно присвоить любое значение, но переменную типа unknown нельзя присвоить переменной другого типа, без проверки на совместимость типов. Так нельзя получать и добавлять свойства переменной типа unknown

let bar: 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
}

// Перед тем как работать с свойствами нужно проверить их наличие
if (hasXYZ(bar)) {
    // ... теперь мы получили доступ к свойствам
    bar.x.prop;
    bar.y.prop;
    bar.z.prop;
}

Поддержка defaultProps в JSX

Если вы когда нибудь использовали параметры по умолчанию в TypeScript/JavaScript, вы наверное знаете насколько это удобно при вызове функций. В React существует похожая концепция для свойств компонента. Во время создание нового элемента компонента, React проверяет свойство defaultProps, чтобы передать его в props, если они не были указаны.

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",
    };
}
const res = ReactDOM.renderToString(<Greet />);

Обратите внимание на то что, в , не нужно задавать свойство name. Когда элемент Greet будет создаваться его значение выводится из defaultProps. К сожалению TypeScript не понимает , что defaultProps имеет какое-либо отношение к вызовам JSX. Вместо этого, нужно определять свойства как необязательны и делать про проверку на существование свойства в функции render.

export interface Props { name?: string }
export class Greet extends React.Component<Props> {
    render() {
        let { name } = this.props;
        if(!name){
          name='Default';
        }
        return <div>Hello ${name.toUpperCase()}!</div>;
    }
    static defaultProps = { name: "world"}
}

В TypeScript 3.0 был добавлен новый псевдоним типа для пространства имен JSX — LibraryManagedAttributes, несмотря на длинное имя, это всего лишь вспомогательный тип который говорит TypeScript какие атрибуты JSX тег принимает


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"}
}
let el = <Greet />

Директива /// <reference lib=»…» />

Многие разработчики сталкивались с проблемой что полифиллы(библиотеки которые добавляют новые API в старые браузеры), часто имеют свои собственные файлы для определения типов(.d.ts файлы) который пытаются сами определить типы для API которые они реализуют. В некоторых случаях это хорошо, но эти определения глобальны и могут конфликтовать с встроенным файлом lib.d.ts. Например декларации типов для core-js могут конфликтовать с встроенным файлом lib.es2015.d.ts.

Чтобы решить эту проблему в TypeScript 3.0 был добавлен новый способ для определения типов встроенных API

/// &amp;lt;reference lib="es2015.promise" /&amp;gt;
export {};