
В 2012 году разработчики C# из компании Microsoft создали язык TypeScript — надмножество JavaScript. Он предназначен для разработки больших приложений, от100 тысяч строк. Давайте на примерах рассмотрим синтаксис TypeScript, его основные достоинства и недостатки, а также разберём способ взаимодействия с популярными библиотеками.
Кому это будет полезно: Web-разработчикам и разработчикам клиентских приложений, интересующимся возможностью практического применения языка TypeScript.
Существуют различные инструменты, которые позволяют писать компилируемый в JavaScript код: CoffeeScript, Dart, Uberscript и другие. К ним относится и язык программирования TypeScript, позволяющий исправить некоторые недостатки, присущие JavaScript.
Недостатки JavaScript
- Отсутствие модульности — могут возникать проблемы из-за конфликта больших файлов.
- Нелогичное поведение.
- Динамическая типизация — нет IntelliSense, из-за чего мы не видим, какие ошибки будут возникать во время написания, а видим их только во время исполнения программы.
Основные достоинства TypeScript
- Статическая типизация.
- Компилируется в хороший JavaScript.
- Наличие инструментов для разработчика.
- Нативный код хорошо читается, в нём легко разобраться.
- TypeScript поддерживается очень многими инструментами для разработки: Visual Studio, PHP Storm и другие IDE.
- Поддерживается ECMAScript 6.0.
- Компилятор найдёт и выдаст ошибку несоответствия типов до начала компилирования.
Типы переменных, которые поддерживает TypeScript
- Number
- String
- Boolean
- Array
- Enum
- Any
- Void
Они используются для объявления переменных, функций, классов, Generic-типов и других конструкций.
Функции
Параметры функций подразделяются на необязательные и по умолчанию.
- Необязательный параметр
function имя_функции (имя_переменной?:тип): тип_возвращаемого_значения
- Параметр по-умолчанию
function имя_функции (имя_переменной?:тип = "значение"):тип_возвращаемого_значения
- Однотипные параметры
function имя_функции (...имя_переменной?:тип ):тип_возвращаемого_значения
По аналогии с C# знак вопроса после переменной означает, что её значение можно не передавать.
Объявление функции
[csharp] function getCarName(manufacturerName: string, model?: string): string { if (model) { return manufacturerName + " " + model; } return manufacturerName; } [/csharp]
Функции обратного вызова
Мы также можем передавать в качестве параметра функцию.
[csharp] function numberOperation(x: number, y: number, callback: (a: number, b: number) => number) { return callback(x, y); } function addCallBackNumbers(x: number, y: number): number { return x + y; } function multiplyNumbers(x: number, y: number): number { return x * y; } console.log(numberOperation(5, 5, addCallBackNumbers)); // 10 console.log(numberOperation(5, 5, multiplyNumbers)); // 25 [/csharp]
Вызывая функцию
numberOperation, мы передаём два параметра и функцию в качестве callback’а.Объединение и перегрузка функций
Несмотря на строгую типизацию, в TS есть возможность использовать одну и ту же функцию с разными типами передаваемых значений. Например, в зависимости от типа переданного значения, одна функция конкатенирует наши числа, а вторая складывает.
[csharp] //передаём 2 параметра стринг и получаем строку function addOvverload(x: string, y: string): string; //передаём 2 параметра int и получаем int результ function addOvverload(x: number, y: number): number; function addOvverload(x, y): any { return x + y; } [/csharp]
ООП. Классы
Class имя_класса {свойства;методы();constructor(свойства); }[csharp] class Car { var mazda = new Car(1, "Mazda", "6"); console.log(mazda.getCarInfo()); class Car { // объявляются три поля id: number; name: string; model: string; // инициализируется конструктор, создающий модель constructor(carId: number, carModel: string, model: string) { this.id = carId; this.name = carModel; this.model = model; } // будет отображать информацию о автомобиле getCarInfo(): string { return "Car model = " + this.name + " model= " + this.model; } } var mazda = new Car(1, "Mazda", "6"); console.log(mazda.getCarInfo()); [/csharp]
ООП. Статические свойства и функции
Для определения статических функций и свойств используется ключевое слово
static.[csharp] class Formula { static PI: number = 3.14; static Half = 0.5; // расчитывается площадь круга static getСircleSquare(radius: number): number { return this.PI * radius * radius; } // расчитывается площадь треугольника static getTriangleSquare(length: number, height: number): number { return this.Half * length * height; } } // созд. объект класса Formula и расчитываются значения var circle = Formula.getСircleSquare(16); var triangle = Formula.getTriangleSquare(4, 7); console.log("Площадь круга = " + circle); console.log("Площадь треугольника = " + triangle); [/csharp]
ООП. Наследование
Одним из ключевых элементов ООП является наследование, которое в TS реализуется c помощью ключевого слова
extends. При помощи extends мы можем наследовать от базового класса и описать классы наследники.[csharp] interface IAnimal { // свойства интерфейса — два поля и один метод name: string; danger: number; getInfo(): void; } class Animal implements IAnimal { // наследуемся от реализованного интерфейса IAnimal name: string; danger: number; constructor(name: string, danger: number) { this.name = name; this.danger = danger; } getInfo(): void { console.log("Класс Животное. Имя: " + this.name + ", опасность: " + this.danger); } } class Fox extends Animal { tailLength: number; constructor(name: string, danger: number, tailLength: number) { super(name, danger); this.tailLength = tailLength; // ключевое слово super — вызывает конструктор базового класса, // инициализирует объект с начальными описанными в нём свойствами, // а потом инициализируются свойства класса наследника. } getInfo(): void { super.getInfo(); console.log("Класс Лиса. Длина хвоста: " + this.tailLength + " м"); } } var goose: Animal = new Animal("Гусь", 1); goose.getInfo(); var fox: Animal = new Fox("Фоксик", 10, 1); fox.getInfo(); [/csharp]
ООП. Интерфейсы
Для определения кастомного типа данных без реализации в TS (и не только) используются интерфейсы. Чтобы объявить интерфейс, используется ключевое слово
Interface.[csharp] module InterfaceModule { // объявляется интерфейс IAnimal и описываются основные поля и методы этого класса // и потом они реализуются непосредственно на классах наследниках. interface IAnimal { name: string; danger: number; getInfo(): string; } class Animal implements IAnimal { name: string; danger: number; constructor(name: string, danger: number) { this.name = name; this.danger = danger; } getInfo(): string { return this.name + " " + this.danger; } } var seal: IAnimal = new Animal("Тюлень", 1); console.log(seal.getInfo()); } [/csharp]
ООП. Инкапсуляция
Для сокрытия внешнего доступа к состоянию объекта и управления доступом к этому состоянию, в TS используется два модификатора:
public и private.Внутри нашего класса мы можем писать недоступные извне методы и манипулировать с их помощью.
Реализуется это как в C#:
[csharp] module Encapsulation { class Animal { private _id: string; name: string; danger: number; constructor(name: string, danger: number) { // заполнение приватного поля _id при создании метода в конструкторе. this._id = this.generateGuid(); this.name = name; this.danger = danger; } private generateGuid(): string { var d = new Date().getTime(); var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); return uuid; } public getInfo(): string { return "Id = " + this._id + " name = " + this.name + " danger = " + this.danger; } } var parrot: Animal = new Animal("Кеша", 1); console.log(parrot.getInfo()); [/csharp]
Таким образом мы ограничили доступ к методу
generateGuid(). По умолчанию поля и методы имеют доступ public.ООП. Generic
TypeScript позволяет создавать Generic-типы.
function имя_функции(имя_переменной: Т): ТГде Т — тип, которым типизирована функция. Также TS поддерживает типизацию интерфейсов и классов.
Class имя_классаInterface имя_интерфейсаГде T — тип, которым типизирована функция.
[csharp] module GenericModule { function getId<T>(id: T) : T { return id; } // Generic класс Animal, при создании мы передаём тип generic типа и устанавливаем id class Animal<T> { private _id: T; constructor(id: T) { this._id = id; } getId(): T { return this._id; } } var cat = new Animal<number>(16); console.log("Cat id = " + cat.getId()); var dog = new Animal<string>("2327c575-2f7c-46c3-99f2-a267fac1db5d"); console.log("Dog id = " + dog.getId()); } [/csharp]
Передаём тип, устанавливаем Id для определения типа и возвращаем нашему животному информацию. Таким образом используются Generic-типы.
Модули
Одним из недостатков JavaScript является то, что большое количество файлов может пересекаться между собой, и возникают своеобразные конфликты. В TypeScript эту проблему решают модули.
Модули — это пространство имен, внутри которого можно определить классы, перечисления, переменные, функции, другие модули. Любой набор классов, интерфейсов, функций могут быть объединены в какую-то скобку и названы определённым модулем. И потом с этим модулем можно легко и просто взаимодействовать.
Для определения модуля используется ключевое слово
module.[csharp] import test = MyTestModule.MyTestClass; module CarModule { // объявляется модуль CarModule в нём есть интерфейс iCar. export interface ICar { // ключевое слово export говорит нам о том, что данный интерфейс может быть видет извне нашего модуля. Если мы уберем слово export, данный интерфейс будет виден в скобках этого CarModule. id: number; carModel: string; model: string; getCarInfo(): string; } export class Car implements ICar { // создается класс Car, который имплементирует наш ICar id: number; carModel: string; model: string; // если мы удалим это поле, то IntelliSence предупредит о том, что не описано поле model constructor(carId: number, carModel: string, model: string) { this.id = carId; this.carModel = carModel; this.model = model; } getCarInfo(): string { var t = new test().getInfo(); return "Car model = " + this.carModel + " model= " + this.model + " " + t; } } } let car = new CarModule.Car(16, "ВАЗ", "2107"); console.log(car.getCarInfo()); [/csharp]
Изменения в терминологии: важно, что начиная с TypeScript 1.5 изменилась номенклатура. Чтобы не было разногласий с терминологией ECMAScript 2015, «внутренние модули» стали называться «namespaces», а «внешние модули» теперь просто «modules». То есть module X { однозначно с более предпочитаемым namespace X {.
Заголовочные файлы
Для связки с какими-то глобальными переменными необходимо подключать заголовочные файлы — это один из недостатков TypeScript. Чтобы установить связь с внешними файлами скриптов JavaScript в TS, необходимо использовать декларативные или заголовочные файлы с расширением *.d.ts.
Заголовочные файлы основных библиотек уже описаны, и с ними достаточно просто и легко работать. Для этого необходимо зайти на сайт и подключить нужный набор заголовочных файлов, например, jQuery. Затем объявляются основные глобальные переменные, которые используются в jQuery, и в последующем они будут использоваться в TS-файле.
[csharp] /// <reference path="../lib/typings/jquery.d.ts" /> class Cars { private cars: Array<Car> = new Array<Car>(); load(): void { $.getJSON('http://localhost:53923/api/Car', (data) => { this.cars = data; alert('данные загружены'); }); } displayUsers(): void { var table = '<table class="table">'; for (var i = 0; i < this.cars.length; i++) { var tableRow = '<tr>' + '<td>' + this.cars[i].id + '</td>' + '<td>' + this.cars[i].name + '</td>' + '<td>' + this.cars[i].model + '</td>' + '</tr>'; table += tableRow; } table += '</table>'; $('#content').html(table); } } window.onload = () => { var cars: Cars = new Cars(); $("#loadBtn").click(() => { cars.load(); }); $("#displayBtn").click(() => { cars.displayUsers(); }); }; [/csharp]
Недостатки TypeScript
- DefinitelyTyped — в некоторых случаях отсутствуют популярные библиотеки.
- .ts .d.ts .map — большое количество дополнительных файлов после компилирования ts-файла.
- Неявная статическая типизация. Если объявим переменную типа any, то никакой пользы от статической типизации не получим.
Преимущества TypeScript
- Строгая типизация. Огромный плюс — IntelliSence, который на этапе компиляции указывает на ошибки, и их можно исправить до этапа выполнения.
- ООП. Прекрасно поддерживаются все основные принципы ООП.
- Надмножество JavaScript’а. Достаточно скопировать код из JavaScript и вставить в TS, чтобы он заработал
- Разработка сложных решений. Благодаря поддержке модульности крупные команды разработчиков могут создавать большие приложения.
Инструменты
- Visual Studio 2015.
- WebStorm, Eclipse.
- Основные тайпинги https://github.com/DefinitelyTyped/DefinitelyTyped.
Заключение
TypeScript является отличным враппером, который может в значительной мере повысить производительность команды разработчиков, с сохранением совместимости с существующим кодом. TypeScript поддерживается в VisualStudio 2015 и взаимодействует с ECMAScript 6.0. Пока в JavaScript нет строгой типизации, TypeScript — это отличная альтернатива, применение которой не требует значительных затрат времени на изучение.

