Статьи

Преимущества перехода с JavaScript на TypeScript

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

Основные преимущества TypeScript:

  1. Поддержка классов и модулей
  2. Статическая типизация (проверка статического типа для выявления дополнительных ошибок во время компиляции)
  3. Поддержка функций ES6
  4. Очистить определение API библиотеки
  5. Встроенная поддержка упаковки JavaScript
  6. Синтаксис Сходство с нашими внутренними языками (Java,  Scala )
  7. Расширенный набор JavaScript, более легкий для изучения разработчиками JavaScript по сравнению с CoffeeScript или ClojureScript

Поддержка классов и модулей

Ключевые слова, такие как  классинтерфейсрасширяет  и  модуль  доступны в TypeScript.

Вы можете определить класс, как показано ниже:

// class define in TypeScript
class VirtualPageTracker extends Tracker {
private virtualPageName: string = ‘’;
constructor(name) {
super(name);
}

getName(): void {
return this.virtualPageName;
}

static getTrackerName(): string {
return “VirtualPageTracker”
}
}     

 Компилятор TypeScript преобразует его в:

// Transcompiled JavaScript

var __extends = (this && this.__extends) || function (d, b) {

   for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];

   function __() { this.constructor = d; }

   d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

};

var VirtualPageTracker = (function (_super) {

 __extends(VirtualPageTracker, _super);

   function VirtualPageTracker(name) {

       _super.call(this, name);

       this.virtualPageName = ;

   }

   ;

   VirtualPageTracker.prototype.getName = function () {

       return this.virtualPageName;

   };

   VirtualPageTracker.getTrackerName = function () {

       return ;

       VirtualPageTracker;

   };

   return VirtualPageTracker;

})(Tracker);

Статическая печать

var name: string;

name = 2; // type error

function foo(value: number) {}

foo(''); // type error


interface Bar {

    setName: (name: string) => void;

getName: () => string;

}

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

var bar: Bar = {

getName: function() {return 'myName';}

} // type error, setName function is missing in the object assigned to bar.      

Поддержка функций ECMAScript 6

ECMAScript 6 — это текущая версия спецификации языка ECMAScript с расширенными возможностями языка. С TypeScript вы можете начать использовать многие функции ES6, хотя они могут не поддерживаться вашим целевым браузером. TypeScript может быть скомпилирован в соответствии с различными стандартами JavaScript, такими как ES3, ES5 или ES6. Некоторые функции пригодятся, например, следующие. 

Ты можешь написать: 

// for..of loops
var arr = ['a', 'b', 'c'];
for (let item of arr) {
   console.log(item);
}      

Он составлен для: 

// compiled JavaScript

var arr = ['a', 'b', 'c'];

for (var _i = 0; _i < arr.length; _i++) {

   var item = arr[_i];

   console.log(item);

}      

Обратитесь к  Таблице совместимости TypeScript ES6  за дополнительными функциями ES6, которые вы можете использовать.

Очистить определение API

Чтобы другие библиотеки TypeScript могли получить доступ к вашей библиотеке, вам нужно создать файл .d.ts, чтобы объявить все ваши открытые API-интерфейсы вашей библиотеки с информацией о типизации. Это заставляет каждую разрабатываемую вами библиотеку иметь четко определенный API. Мы обнаружили, что это хороший способ отслеживать и поддерживать наши API.

Встроенная поддержка упаковки JavaScript

Вы можете определить одну основную запись .ts файл со списком всех файлов TS в нем. Запустив компилятор TypeScript с параметром –out, компилятор объединит все перечисленные файлы и упомянутые файлы (прямо или косвенно) в один файл js в порядке ссылки.

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

module A {
   function foo() { }
}

module A {
   function bar() {
       foo();
   }
}      

Это генерирует код ниже с ошибкой компиляции «не могу найти имя« foo »»:

var A;
(function (A) {
   function foo() { }
})(A || (A = {}));
var A;
(function (A) {
   function bar() {
       foo();
   }
})(A || (A = {}));      

Функция foo определена в первом вызове анонимной функции для модуля A, невидима во втором вызове анонимной функции, поэтому она должна быть экспортирована как:

module A {
   export function foo() { }
}

module A {
   function bar() {
       foo();
   }
}      

Это генерирует код ниже без ошибок:

var A;
(function (A) {
   function foo() { }
   A.foo = foo;
})(A || (A = {}));
var A;
(function (A) {
   function bar() {
       A.foo();
   }
})(A || (A = {}));      

Проблема здесь в том, что A.foo виден не только модулю A — любой может вызвать его и изменить его сейчас. Не существует видимой концепции уровня модуля, которая должна быть похожа на Java «package-private», когда нет модификатора для классов или членов Java.

Это может быть решено путем генерации:

module A {
   export function foo() { }
}

module A {
   function bar() {
       foo();
   }
}      

чтобы:

var A;
(function (A) {
   function foo() { }

   function bar() {
       foo();
   }
})(A || (A = {}));      

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

Когда я пишу эту статью, я замечаю   аннотацию / * @internal * / в исходном коде компилятора ts; это экспериментальная опция, выпущенная с использованием машинописного текста 1.5.0-alpha, для  удаления объявлений, помеченных как @internal . Это помогает включать только объявления без @internal (который обслуживает ваши внешние API) при создании файла .d.ts из вашего кода. И если ваши конечные пользователи также используют TypeScript, это предотвращает их доступ к вашим внутренним членам.

Создание файла .d.ts для:

module A {
   /* @internal */ export function internal() {}
   export function external() {}
}      

по:

tsc -d --stripInternal A.ts     

будет выводить:

declare module A {
   function external(): void;
}      

Однако, если ваш конечный пользователь все еще использует JavaScript, он все еще может использовать внутреннюю функцию.

Вывод

В целом, переход на TypeScript приятен и полезен. Хотя это добавляет ограничения к вашей реализации JavaScript, вы можете найти хороший обходной путь или реализовать преимущества, которые его перевешивают. Более того, это активный проект с открытым исходным кодом (около 200 коммитов для мастеринга в прошлом месяце) с полезной и содержательной документацией, которая поможет вам легко начать. И только в марте этого года Google также объявил, что они  заменят AtScript на TypeScript . Angular 2 теперь также построен с использованием TypeScript. Пока что переход на TypeScript оказался полезным.