Недавно мы перевели наш браузерный агент 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 оказался полезным.