javascript

Основы TypeScript, необходимые для разработки Angular-приложений

  • среда, 13 декабря 2017 г. в 03:13:39
https://habrahabr.ru/company/ruvds/blog/344502/
  • Разработка веб-сайтов
  • JavaScript
  • Angular
  • Блог компании RUVDS.com


TypeScript — это надмножество JavaScript, то есть, любой код на JS является правильным с точки зрения TypeScript. Однако, TypeScript обладает некоторыми дополнительными возможностями, которые не входят в JavaScript. Среди них — строгая типизация (то есть, указание типа переменной при её объявлении, что позволяет сделать поведение кода более предсказуемым и упростить отладку), механизмы объектно-ориентированного программирования и многое другое. Браузеры не поддерживают TypeScript напрямую, поэтому код на TS надо транспилировать в JavaScript.

image

TypeScript применяется для разработки веб-приложений с использованием популярного фреймворка Angular. В этом материале мы рассмотрим основы TypeScript, необходимые для того, чтобы приступить к освоению Angular и к работе с ним.

Предварительная подготовка


Прежде чем пользоваться TypeScript, его надо установить:

sudo npm install -g typescript

Теперь можно переходить к изучению возможностей языка. Откройте текстовый редактор и создайте новый файл. Поместите в него следующий код:

function log(message) {
  console(message);
}
let message = 'Hello Typescript';
log(message); 

Сохраните его как main.ts и, перейдя в терминале туда, где его сохранили, выполните следующую команду:

tsc main.ts

Данная команда создаёт новый файл, main.js, который является транспилированной JS-версией файла main.ts. Этот файл можно, например, выполнить с помощью Node.js:

node main.js

Обратите внимание на то, что вам не нужно выполнять команду tsc при сборке Angular-приложения, так как инструмент ng serve подготовит код к выполнению автоматически.

Типы данных в TypeScript


TypeScript поддерживает различные типы данных. Среди них можно отметить следующие:

let a: number      //например: 1, 2, 3
let b: boolean     //например: true, false
let c: string      //например: "abel agoi"
let d: any         //такая переменная может содержать значения любых других типов
let e: number[]    //числовой массив, например: [1, 3, 54]
let f: any[]       //массив значений любых типов, например: [1, "abel agoi", true]

Обратите внимание на то, что TypeScript поддерживает ещё один тип, enum, о нём вы можете почитать самостоятельно.

Стрелочные функции


В JavaScript функции объявляют так:

let log = function (message) {
  console.dir(message); 
}

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

let log = (message) => { //тут мы просто убираем слово function 
 console.dir(message);
}
//это можно сократить, приведя к следующему виду
let log = (message) => console.dir(message);
//а если функции передаётся всего один параметр, можно записать её ещё короче
let log = message => console.dir(message); //однако, такой код плохо читается

Интерфейсы


Не рекомендуется писать функции, которым надо передавать очень много параметров. Например, это может выглядеть так:

let myFunction = (
  a: number, 
  b: number, 
  c: string, 
  d: any, 
  e: number[], 
  f: any[]
) => {
 console.log('something);
}

Избежать таких конструкций можно, включив параметры в объект и передав функции единственный объект. Тут нам помогут интерфейсы. Этот механизм есть в TypeScript:

interface MyParameters {
  a: number,
  b: number,
  c: string,
  d: any,
  e: number[],
  f: any[]
  ...
  ...
}
let myFunction = (myParameters: MyParameters) {
}

Классы


Стоит выработать у себя привычку группировать связанные переменные (свойства) и функции (методы) в единую конструкцию, которая в программировании называется классом. Для того, чтобы опробовать это на практике, создайте файл myPoints.ts и поместите в него следующий код:

class MyPoint {
  
   x: number;
   y: string;
   draw() {
     //обратите внимание на то, что со свойством x мы работаем через this
     console.log("X is: " + this.x);
     console.log("X is: " + this.y);
   }
   getDistanceBtw(another: AnotherPoint) {
    //посчитать и вернуть расстояние
   }
}

Доступ к свойствам и методам классов


Мы сгруппировали связанные переменные и методы в единый класс. Теперь надо разобраться с тем, как с ними работать. Для этого нужно создать экземпляр класса:

let myPoint = new MyPoint() //MyPoint - это имя класса
myPoint.x = 2;              //устанавливаем свойство x 
myPoint.y = "a";            //устанавливаем свойство y

myPoint.draw();             //вызываем метод draw

Файл myPoints.ts можно транспилировать и запустить то, что получилось:

tsc myPoint.ts | node myPoint.js

Обратите внимание на то, что прежде чем назначать свойствам класса значения, нужно создать его экземпляр. А есть ли способ задания значений свойств в ходе создания экземпляра класса? Такой способ есть и существует он благодаря конструкторам.

Конструкторы


Конструктор — это метод, который вызывается автоматически при создании экземпляров класса. Конструктор позволяет задавать значения свойств. Вот пример работы с экземпляром класса, в котором возможности конструкторов не применяются:

let myPoint = new MyPoint()
myPoint.x = 2;  
myPoint.y = "a";

То же самое, с использованием конструктора, можно переписать так:

let myPoint = new MyPoint(2, "a");

Однако, для того, чтобы вышеприведённый пример заработал, понадобится внести изменения в класс, задав его конструктор:

class MyPoint {
  
   x: number;
   y: string;
   constructor (x: number, y: string) {
     this.x = x;
     this.y = y;
   }
   draw() {
     //обратите внимание на то, что со свойством x мы работаем через this
     console.log("X is: " + this.x);
     console.log("X is: " + this.y);
   }
   getDistanceBtw(another: AnotherPoint) {
    //посчитать и вернуть расстояние
   }
}

Необязательные параметры в конструкторе


Что если мы решили использовать конструктор, но хотим, чтобы явное задание параметров при создании экземпляров класса было бы необязательно? Это возможно. Для этого надо использовать знак вопроса (?) в конструкторе. Этот знак позволяет определять параметры, которые, при создании экземпляра класса, задавать необязательно:

class MyPoint {
  
   x: number;
   y: string;
   constructor (x?: number, y?: string) { //обратите внимание на "?" перед ":"
     this.x = x;
     this.y = y;
   }
   draw() {
     //обратите внимание на то, что со свойством x мы работаем через this
     console.log("X is: " + this.x);
     console.log("X is: " + this.y);
   }
   getDistanceBtw(another: AnotherPoint) {
    //посчитать и вернуть расстояние
   }
}


//Этот код нормально отработает при создании экземпляра myPointA класса MyPoint
let myPointA = new MyPoint()
myPoint.x = 2;  
myPoint.y = "a";
myPoint.draw();
//Этот код нормально отработает при создании экземпляра myPointB класса MyPoint
let myPointB = new MyPoint(2, "b");
myPointB.draw();
//Этот код нормально отработает при создании экземпляра myPointС класса MyPoint
let myPointC = new MyPoint(2); //обратите внимание на то, что значение Y мы тут не передаём
myPointC.draw();

Модификаторы доступа


Модификатор доступа — это ключевое слово, которое используется со свойством или членом класса для управления доступом к нему извне. В TypeScript есть три модификатора доступа: public, protected и private. По умолчанию все члены класса общедоступны — это аналогично использованию с ними модификатора доступа public, то есть, читать и модифицировать их можно извне. Использование модификатора доступа private позволяет запретить внешним механизмам работу с членами класса. Например, здесь мы использовали данный модификатор со свойствами x и y:

class MyPoint {
 ...
 
 private x: number;
 private y: string;
 //модификаторы доступа можно устанавливать и для методов
 public draw() {
   //нарисовать что-нибудь
 }
 ...
}
let myPoint = new MyPoint();
myPoint.x = 3;

Попытка использовать конструкцию myPoint.x при работе с экземпляром класса приведёт к ошибке, так как свойство объявлено с модификатором доступа private.

Вспомогательные средства конструкторов


Выше мы добавляли конструктор к нашему классу следующим образом:

private x: number;
public  y: string;
constructor (x: number, y: string) {
     this.x = x;
     this.y = y;
}

TypeScript позволяет записать то же самое в сокращённой форме:

constructor (private x: number, public y: string) {}

Всё остальное будет сделано автоматически (готов поспорить, вы часто будете с этим встречаться в Angular-приложениях). То есть, нам не нужен следующий код:

private x: number;
public  y: string;
и
this.x = x;
this.y = y;

Геттеры и сеттеры


Предположим, что сейчас класс MyPoint выглядит так:

class MyPoint {
   constructor (private x?: number, private y?: string) {}
   draw() {
     //нарисовать что-нибудь
   }
   drawAnotherThing() {
    //нарисовать ещё что-нибудь
   }
}

Мы совершенно точно знаем, что не сможем работать со свойствами x и y за пределами экземпляра класса MyPoint, так как они объявлены с использованием модификатора доступа private. Если же нужно как-то на них влиять или читать их значения извне, нам понадобится использовать геттеры (для чтения свойств) и сеттеры (для их модификации). Вот как это выглядит:

class MyPoint {
   constructor (private x?: number, private y?: string) {}
   getX() { //возвращает X
     return this.x;
   }
   setX(value) { //записывает в X переданное методу значение
     this.x = value;
   }
}
//так как установить x напрямую нельзя, после инициализации экземпляра класса myPoint
//мы использует сеттер setX() для установки значения X
let myPoint = new MyPoint();
myPoint.setX = 4;
console.log( myPoint.getX() ); //будет выведено 4;

Механизм сеттеров и геттеров позволяет задавать ограничения, применяемые при установке или чтении свойств класса.

Вспомогательные механизмы для работы с сеттерами и геттерами


Вместо того, чтобы использовать конструкцию вида myPoint.setX() для того, чтобы установить значение x, что если можно было бы поступить примерно так:

myPoint.X = 4; //работа с X так, как будто это свойство, хотя на самом деле это сеттер

Для того, чтобы подобный механизм заработал, при объявлении геттеров и сеттеров нужно поставить перед именем функций ключевые слова get и set, то есть, сравнивая это с предыдущими примерами, отделить эти слова пробелом от следующего за ними имени функции:

class MyPoint {
   constructor (private x?: number, private y?: string) {}
   get X() { //обратите внимание на пробел перед X
     return this.x;
   }
   set X(value) { //обратите внимание на пробел перед Y
     this.x = value;
   }
}

Кроме того, общепринятой практикой является использование знаков подчёркивания в начале имён свойств (_):

class MyPoint {
   constructor (private _x?: number, private _y?: string) {}
   get x() { //обратите внимание на пробел перед X
     return this._x;
   }
   set x(value) { //обратите внимание на пробел перед Y
     this._x = value;
   }
}

Модули


Когда вы приступите к созданию реальных приложений, вы обнаружите, что в них понадобится далеко не один класс. Поэтому хорошо бы найти средство, позволяющее создавать классы так, чтобы их можно было использовать в других файлах, и в классах, объявленных в этих файлах, то есть, нам нужны инструменты написания модульного кода. Такие инструменты уже имеются в TypeScript. Для того, чтобы их рассмотреть, приведём код в файле myPoint.ts к такому виду:

export class MyPoint { //обратите внимание на новое ключевое слово export
   constructor (private _x?: number, private _y?: string) {}
   get x() { 
     return this._x;
   }
   set x(value) { //обратите внимание на пробел перед x
     this._x = value;
   }
   draw() {
     //нарисуем что-нибудь
   }
}

Благодаря ключевому слову export класс, описанный в файле MyPoint.ts, можно сделать видимым в других файлах, импортировав его в них с использованием ключевого слова import.

Для того, чтобы, например, пользоваться классом MyPoint в файле main.ts, его надо импортировать:

import { MyPoint } from './myPoint'; 
class Main {
   let MyPoint = new MyPoint(4, "go to go");
   MyPoint.draw();   
} 

Обратите внимание на то, что файлы main.ts и myPoint.ts находятся в одной и той же директории.
image

Итоги


В этом материале мы рассмотрели основы TypeScript, которые необходимы для того, чтобы начать писать приложения на Angular. Теперь вы сможете понять устройство Angular-кода, а значит, у вас, кроме прочего, появится возможность эффективно осваивать руководства, которые посвящены этому фреймворку и предполагают знание TS.

Уважаемые читатели! Если вы хорошо разбираетесь в Angular — просим рассказать новичкам о том, как вы его изучали, и посоветовать хорошие учебные материалы по нему.