Недавно мы перевели наш браузерный агент RUM из JavaScript в TypeScript . В своем последнем посте я сосредоточился на том, чтобы пройти через этапы миграции с JavaScript, проблемы и лучшие практики, которые мы обнаружили на этом пути. Этот будет посвящен более подробной информации о преимуществах и одной из предложенных нами функций.
Основные преимущества TypeScript:
- Поддержка классов и модулей
- Статическая типизация (проверка статического типа для выявления дополнительных ошибок во время компиляции)
- Поддержка функций ES6
- Очистить определение API библиотеки
- Встроенная поддержка упаковки JavaScript
- Синтаксис Сходство с нашими внутренними языками (Java, Scala )
- Расширенный набор 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 оказался полезным.