В 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 — это отличная альтернатива, применение которой не требует значительных затрат времени на изучение.