вторник, 31 июля 2018 г.

Нужно ли переходить с JavaScript на TypeScript?

TypeScript (TS) и JavaScript (JS) два широко используемые языка в среде разработки, но в чем их отличия и в где лучше использовать тот или другой? В данной публикации мы сравним два языка, посмотрим на связи между ними, поговрим об основных отличиях и выделим основные преимущества каждого из них.
Определение TypeScript
TypeScript это синтаксический суперсет JavaScript с открытым исходным кодом, который компилируется в JavaScript (EcmaScript 3+). TypeScript предлагает аннотации типов которые обеспевивают опциональную статическую проверку типов во время компиляции. Поскольку это суперсет JavaScript, то любой валидный код JavaScript будет является валидными кодом TypeScript. Но это не значит что любой код JavaScript может быть обработан компилятором TypeScript.
let a = 'a';
a = 1; // throws: error TS2322: Type '1' is not assignable to type 'string'.

Преимущества TypeScript
Аннотации типов
TypeScript был создан для “статической идентификации конструкций, которые похожи на ошибки”. Это позволяет нам сделать более безопасные предположения о состоянии во время выполнения. Давайте сравним функции JavaScript и TypeScript:
// Basic JavaScript
function getPassword(clearTextPassword) {
    if(clearTextPassword) {
        return 'password';
    }
    return '********';
}

let password = getPassword('false'); // "password"

В JavaScript ничего не помешает вызову getPassword(…) с неправильными  (не-булевыми) параметрами, что приведет к тихой ошибке во время выполнения. Это можно полностью избежать во время компиляции с использованием аннотаций типов TypeScript:
// Written with TypeScript
function getPassword(clearTextPassword: boolean) : string {
    if(clearTextPassword) {
        return 'password';
    }
    return '********';
}

let password = getPassword('false'); // throws: error TS2345: Argument of type '"false"' is not assignable to parameter of type 'boolean'.

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

Документация API
Давайте предположим, что функция getPassword(…) описанная выше находится во внешней библиотеке. Как я, как потребитель библиотеки, буду знать тип аргумента, который необходимо передать в функцию? Существуют комментарии jsdoc которые поддерживаются большим количеством IDE и редакторов, такими как VSCode. Также у библиотеки есть собственная документация, которая с помощью инструмента Dash становится более доступной. Но ни одно из этих средств не обеспечивает такого удобства которое есть у TypeScript.
Давайте для примера рассмотрим fetch API. Скриншот показаный ниже демонстрирует как мы можем исследовать API используя функциональность VSCode просмотра определения. Используя это средство мы можем быстро исследовать типы входных параметров (RequestInfo = Request | string and RequestInit) и тип возврата (Promise<Response>). Эти средства предоставляют практически то же, что и классический JavaScript и jsdocs.
Заблуждения
Существует много заблуждений касательно того почему стоит выбирать TypeScript. Попытаемся развеять некоторые из них.
  • Возможности ES6: Одна из самых главных причин, почему стоит выбрать TS - это желание использовать возможности ES6, такие как модули, классы стрелочные функции и прочее. Однако это не оченб хорошая причина, почему стоит выбирать TS, потому что того же можно добиться используя Babel. В реальности не совсем привычно видеть TypeScript и Babel в одном проекте.
  • Он проще, чем JavaScript: По большей части это утверждение субъективно, но есть неопровержимые аргументы, что TS добавляет синтакцический шум. Однако самый важный момент – это то, что TS не скрывает JS. Это не повод для пренебрежения основами JavaScript. TS по-прежнему является надмножеством JavaScript и не обеспечивает защиту от многих распространенных жалоб на JavaScript, которые не связаны с отсутствием проверки статического типа (this, пространства, прототипы и т.д.). Из-за этого разработчики должны по-прежнему поддерживать высокие знания JS.
  • Корректность типов == корректность программы: Хотя это может казаться и очевидным, что утверждение некорректно, я верю в то, что статическая проверка типов обеспечивает исскуственную безопасность, которой разработчики могут пользоваться, и это стоит обсудить. Есть корректность типов  не влияет на корректность программы, то что же нам нужно сделать для того что бы постоянно и последовательно убеждаться в том что программа делает то что нужно? Самый лучший ответ на этот вопрос – это юнит-тестирование. Таким образом возникает вопрос, что если мы будем использовать юнит тестирование для проверки корректности программы, то они могут и предотвращать от большинства ошибок связанных с ошибками типов.
  • Статические типы дают вам Встряхивание дерева (Tree-Shaking): Встряхивание дерева – это игнорирование «мертвого кода» с помощью статических конструкций, таких как импорт/экспорт именованых модулей и const. В нынешнее время, TypeScript не поддерживает встряхивание дерева из коробки.

Сравнения синтаксиса и компиляции
Часто можно услышать что разработчики выбирают TypeScript изза таких возможностей как модули и классы. Однако, важно понимать что эти вещи уже появились и в JavaScript в ES6 и можно использовать Babel для транспиляции вниз к ES5 для лучшей совместимости браузеров. Изза этого недоразумения, ниже представлено сравнение синтаксиса с последними возможностями EcmaScript. Для каждой из возможностей, вы увидите версию на TypeScript и ее аналог компилированный в ES5 JavaScript вместе с динамическим определением ES6  преобразованным в ES5 с помощью babel.
Классы
// -- TypeScript -- //
class Article {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

// -- TypeScript compiled output -- //
var Article = /** @class */ (function () {
    function Article(name) {
        this.name = name;
    }
    return Article;
}());
// -- JavaScript with Babel -- //
class Article {
    constructor(name) {
        this.name = name;
    }
}

// -- Babel compiled output -- //
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Article = function Article(name) {
    _classCallCheck(this, Article);

    this.name = name;
};
Модули
// -- TypeScript -- //
export default class Article { }

// -- TypeScript compiled output -- //
define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var Article = /** @class */ (function () {
        function Article() {
        }
        return Article;
    }());
    exports.default = Article;
});
// -- JavaScript with Babel -- //
export default class Article { }

// -- Babel compiled output -- //
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Article = function Article() {
  _classCallCheck(this, Article);
};

exports.default = Article;
Опциональные параметры
// -- TypeScript -- //
function log(message: string = null) { }

// -- TypeScript compiled output -- //
function log(message) {
    if (message === void 0) { message = null; }
}
// -- JavaScript with Babel -- //
function Log(message = null) { }

// -- Babel compiled output -- //
"use strict";

function Log() {
  var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
}
Когда выбирать: TypeScript vs. JavaScript
TypeScript
  • Предпочитаемая проверка типов на этапе компиляции: Абсолютно возможно выполнить проверу типов во время выполнения с использованием ванильного JavaScript. Однако это приводит к избыточному коду во время выполнения, которое может быть ликвидировано с помощью валидации в процессе компиляции.
  • Работа с новым фреймворком или библиотекой:Давайте представим что вы принимаете участие в новом проекте React. Вы незнакомы с API React, но поскольку у вас есть определения типов, вы можете использовать intellisense который поможет вам разобраться с новыми интерфейсами.
  • Большой проект и много разработчиков: TypeScript очень удобен, когда работаете на большом проекте или если у вас на проекте работает несколько разработчиков вместе. Используя интерфейсы и модификаторы доступа TypeScript можно добиться куда большего при работе с API (для определения доступных членов класса для использования).

JavaScript
  • Требуются средства построения: TypeScript требует один дополнительный этап, который сгенерирует финальный JavaScript для выполнения. Однако, в наши дни становится очень редкими случаи разработки приложений JavaScript без инструментов построения любого вида.
  • Маленькие проекты: TypeScript может быть избыточным для маленьких проектов и небольших команд, с небольшим количеством кода.
  • Серьезный воркфлоу для тестирования: Если у вас есть сильная JavaScript команда, которая уже применяет test-driven разработку, перевод проекта на TypeScript может вам не дать достаточных возможностей для этого.
  • Добавленные зависимости: Для того что бы использовать библиотеки с TypeScript, вам необходимо иметь определения типов для этих библиотек. Каждое определение типов подразумевает отдельный пакет npm. Наличие этих дополнительных зависимостей добавляет новые риски в поддержке вашего проекта в корректном состоянии.Если вы не будете импортировать определения типов, то вы потеряете основные преимущества TypeScript. Обратите внимание, что существует проект DeninitelyTyped который как раз  и предназначен для борьбы с этими рисками. Чем более популярная библиотека, тем больше шансов что ее определения типов будут поддерживатся в обозримом будущем.
  • Неподдерживаемые фреймворки: Если фреймворк, который вы выбрали не поддерживается TS, например EmberJS (хотя это запланировано и является языком выбора для Glimmer), то опять таки использование TypeScript не дает вам никаких преимуществ.

Прочие соображения
Таким образом вы определились, что пора добавться поддержку типов в ваш процесс Frnot-end разработки. Для этого можно использовать только TypeScript? Короткий ответ – нет. В реальности, существуют как минимум еще два больших инструмента для аннотаций типов, на которые стоит обратить внимание но которые не описаны в нашей статье.