Вводный курс по TypeScript

Обложка поста

Перевод статьи «A crash course in TypeScript»

TypeScript — это расширенная версия JavaScript, главной целью которого является упрощение разработки крупных JS-приложений. Этот язык добавляет много новых принципов — классы, дженерики, интерфейсы, статические типы, что позволяет разработчикам использовать разные инструменты, такие как статический анализатор или рефакторинг кода.

Стоит ли использовать TypeScript?

В первую очередь возникает вопрос: а какие преимущества у этого языка?

  1. Статическая типизация. JavaScript — это язык с динамической типизацией, то есть компилятор не знает, что за тип переменной вы используете, пока эта переменная не будет инициализирована. Подобные вещи могут вызвать трудности и ошибки в ваших проектах. В TypeScript появляется поддержка статической типизации, что при грамотном использовании исключает ошибки, связанные с ошибочной типизацией переменной. При этом динамическая типизация вовсе не пропадает, и ей можно пользоваться.
  2. Лучшая поддержка в IDE. Основным преимуществом языка TypeScript перед JavaScript является лучшая поддержка со стороны IDE, что включает Intellisense, информацию компилятора в реальном времени, отладку и многое другое. Также существуют различные расширения, которые помогают в процессе разработки.
  3. Доступ к новым возможностям ECMAScript. В TypeScript есть поддержка новых возможностей ECMAScript, поэтому можно разрабатывать приложения с помощью новейших инструментов, не волнуясь о поддержке их браузером.

В каких случаях стоит использовать TypeScript?

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

  1. В случае крупного приложения. TypeScript отлично подойдёт, если ваше приложение имеет большую архитектуру и кучу кода, а особенно если над этим приложением работают несколько разработчиков.
  2. В случае, когда вы и ваша команда знакомы с ЯП со статической типизацией. Команде стоит смотреть в сторону TypeScript, когда они хотят разрабатывать приложение на JavaScript и уже знакомы с Java или C#, ведь те являются языками со статической типизацией.

Установка TypeScript

Установить TypeScript совсем не сложно — достаточно загрузить его через пакетный менеджер npm и создать TypeScript-файл:

npm install -g typescript

После его установки можно сразу перейти к рассмотрению возможностей этого языка и его синтаксиса.

Типы переменных

Number

Все числовые переменные в TypeScript существуют в виде числа с плавающей запятой. Числовой тип получают даже двоичные и шестнадцатеричные числа:

let num: number = 0.222;
let hex: number = 0xbeef;
let bin: number = 0b0010;

String

Как и другие языки, TypeScript использует тип String для хранения текстовой информации:

let str: string = 'Hello World!';

Можно создавать и многострочные переменные, а также в строки можно вставлять выражения, если выделить строку символами ``:

let multiStr: string = `Двухстрочная
переменная`
let expression = 'Новое выражение'
let expressionStr: string = `Выражение str: ${ expression }`

Boolean

Куда же без одного из основного типа данных:

let boolFalse: boolean = false;
let boolTrue: boolean = true;

Присвоение типов

Основной способ присвоения типа переменной — написание его после самого имени переменной через символ :.

Одиночный тип переменной

Простой пример, где присваивается значение переменной типа String:

let str: string = 'Hello World'

Такой способ действителен для всех типов данных.

Мультитип переменной

Переменной можно присваивать несколько типов, перечисляя их через оператор |.

let multitypeVar: string | number = 'String'
multitypeVar = 20

В коде выше переменной назначается два типа: строчный и численный. Теперь переменной можно присваивать как текстовые данные, так и числовые.

Проверка типов

Ниже будут описаны два основных (на деле их существует больше) способа проверки типа переменной.

Typeof

Команда typeof работает только с базовыми типами данных. Это означает, что эта команда может определить только типы, описанные выше.

let str: string = 'Hello World!'
if(typeof str === number){
 console.log('Str — это число')
} else {
 console.log('Str — это не число')
}

В коде выше создаётся переменная типа String, а потом проверяется, является ли эта переменная числом (что, естественно, всегда будет возвращать false).

Instanceof

Это оператор, который работает почти так же, как typeof. Отличие только в том, что это оператор может определять не только базовые типы, но и собственные.

class Human{
 name: string;
 constructor(data: string) {
  this.name = data;
 }
}
let human = new Human('Gabriel')
if(human instanceof Human){
 console.log(`${human.name} человек`)
}

В коде выше создаётся собственный тип, а потом инициализируется переменная этого типа. Далее этот тип переменной сравнивается с типом Human, что, в данном случае, возвращает true.

Тип Assertions

Иногда приходится преобразовывать (кастовать) переменную одного типа в другой тип. Особенно часто это случается, когда переменную типа any (или другого произвольного типа) нужно передать в функцию, которая принимается аргумент определённого типа.

Существует много решений этой задачи, но ниже будут описано два самых популярных из них.

Ключевое слово as

Чтобы кастовать переменную, нужно после оператора as написать тип, в который переводится переменная.

let str: any = 'Текстовая переменная'
let strLength = (str as string).length

В этом коде текстовая переменная str кастуется в тип String, а поэтому можно использовать параметр length (это сработает и без кастования, если есть соответствующее разрешение в настройках TSLINT).

Оператор <>

Выполняет абсолютно такую же работу, что и ключевое слово as.

let str: any = 'Текстовая переменная'
let strLength = (<string>str).length

Этот код работает идентично предыдущему — разница только синтаксическая.

Массивы

Массивы в TypeScript представляют собой коллекцию одного типа объектов и могут быть созданы двумя способами.

Создание массива

Используя []

Можно создать массив, написав после типа элемента оператор [], тем самым обозначив эту переменную как массив:

let strings: string[] = ['Hello', 'World', '!']

Этот код создает массив элементов String, содержащий 3 разных элемента.

Используя дженерики

Создать массив можно с помощью дженерик-типа (обобщённого типа), написав Array<Type>:

let numbers: Array<number> = [1, 2, 3, 4, 5]

Этот код создаёт числовой массив, содержащий 5 элементов.

Мультитипные массивы

Кроме того, здесь можно создавать массивы с элементами нескольких разных типов. Перечислять типы нужно через оператор |.

let stringsAndNumbers: (string | number)[] = ['Age', 20]

В этом коде создаётся массив, который может содержать как текстовые переменные, так и числовые.

Многомерные массивы

TypeScript поддерживает многомерные массивы — можно сохранять массивы в других массивах. Создать такой массив можно через множественный оператор [].

let numbersArray: number[][] = [[1,2,3,4,5], [6,7,8,9,10]]

Тут создаётся массив, элементами которого являются другие массивы, которые, в свою очередь, содержат числа.

Кортежи

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

let exampleTuple: [number, string] = [20, 'https://tproger.ru'];

В этом примере создаётся кортеж c числом на позиции 0 и текстовой переменной на позиции 1.

Внимание При некорректном присвоении элемента будет выбрасываться исключение.

Пример того, как делать не нужно:

let exampleTuple: [string, number] = [20, 'https://tproger.ru'];

Enum (перечисление)

В TypeScript, как и в других объектно-ориентированных языках, существуют Enum (перечисления). Они позволяют определять именованные константы. В этом языке так же существует возможность создавать текстовые и числовые константы. Определяются перечисления ключевым словом enum.

Числовые константы

Ниже идёт пример числового перечисления, где каждому значению сопоставляется число.

enum State{
 Playing = 0,
 Paused = 1,
 Stopped = 2
}

Такое же перечисление (где первое значение равно 0, второе — 1 и т. д.) можно добиться и таким кодом:

enum State{
 Playing,
 Paused,
 Stopped
}

Текстовые константы

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

enum State{
 Playing = 'PLAYING',
 Paused = 'PAUSED',
 Stopped = 'STOPPED'
}

Объекты

Объект в TypeScript является сущностью, которая содержит набор пар ключ-значение. Значение может быть переменной, массивом или даже функцией. Объект рассматривается как отдельный тип переменной, не относящийся к примитивным.

Создаётся объект в фигурных скобках:

const human = {
 firstName: 'Frank',
 age: 32,
 height: 185
};

В коде выше создаётся объект human с 3 разными парами ключ-значение.

А вот как создавать функцию в объектах:

const human = {
 firstName: 'Старец',
 age: 32,
 height: 185,
 greet: function(){
  console.log("Приветствую тебя, путник!")
 }
};

Собственные типы

В TypeScript есть возможность создавать свои типы, называемые алиасами (англ. alias). Создаётся собственный тип через ключевое слово type.

type Human = {firstName: string, age: number, height: number}

В примере выше создаётся собственный тип Human, содержащий 3 разных свойства. Пример создания объекта типа Human:

const human: Human = {firstName: ‘Franz’, age: 32, height: 185}

Аргументы функций и возвращаемые типы

В TypeScript можно передавать тип аргумента функций и указывать тип возвращаемого значения. Как это выглядит:

function printState(state: State): void {
 console.log(`The song state is ${state}`)
}

function add(num1: number, num2: number): number {
 return num1 + num2
}

В примере выше создаются две функции, обе имеют аргументы с определёнными типами. Во второй функции так же определён тип возвращаемого значения.

Вызываются функции точно так же, как и в обычном JavaScript, но компилятор проверяет правильность передаваемых параметров, и в случае чего выводит ошибку.

add(2, 5)
add(1) // "Error to few parameters"
add(5, '2') // "Error the second argument must be type number"

Опциональные (необязательные) аргументы

Аргумент функции можно сделать необязательным, поставив после него оператор ?. Вот как это выглядит:

function printName(firstName: string, lastName?: string) {
 if (lastName) 
  console.log(`Firstname: ${firstName}, Lastname: ${lastName}`);
 else 
  console.log(`Firstname: ${firstName}`);
}

В примере выше параметр lastName является необязательным, и если он не будет передан, компилятор не выдаст ошибку.

printName('Gabriel', 'Tanner')
printName('Gabriel')

Эти две строки будут исполнены без ошибок.

Значения по умолчанию

Для аргументов функции можно назначать значение по умолчанию. Это значение будет присваиваться аргументу тогда, когда при вызове функции значение самого аргумента не было передано. Пример:

function printName(firstName: string, lastName: string = 'Tanner') {
 console.log(`Firstname: ${firstName}, Lastname: ${lastName}`);
}

В коде выше аргументу lastName присваивается значение по умолчанию, и теперь его не нужно передавать каждый раз при вызове функции (если не нужно передать отличающееся значение, естественно).

Интерфейсы

Интерфейсы содержат свойства и методы кастомного типы, но не содержат их реализацию. Реализацию берёт на себя класс, реализующий интерфейс. Для ясности вот пример:

interface Person{
 name: string
}
const person: Person = {name: 'Gabriel'}
const person2: Person = {names: 'Gabriel'} // Тип Person не присваивается

В примере выше в первом свойстве реализуется интерфейс Person. Попытка реализации интерфейса в переменной person2 выбросит исключение.

Опциональные (необязательные) свойства

При реализации интерфейса можно реализовывать не все его свойства. Чтобы сделать свойство необязательным, после имени свойства нужно поставить оператор ?. Пример:

interface Person{
 name: string
 age?: number
}
const person: Person = {name: 'Frank', age: 28}
const person2: Person = {name: 'Gabriel'}

В этом коде создаются два свойства: обычное и необязательное. Теперь при реализации интерфейса в переменной person2 исключение вызываться не будет.

Read-only свойства

Такие свойства используются тогда, когда нужно изменять свойство только в самом начале, при реализации, а потом свойство становится доступным только для чтения. Для этого используйте ключевое слово readonly.

interface Person{
 name: string
 readonly id: number
 age?: number
}
const person: Person = {name: 'Gabriel', id: 3127831827}
person.id = 200 // Изменить значение уже не получится

Barrel

Barrel-файлы дают возможность свести нескольких экспортируемых модулей в один более удобный.

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

export * from './person';
export * from './animal';
export * from './human';

И после этого можно одной строкой можно импортировать все эти модули вместе:

import { Person, Animal, Human } from 'index';

Generic

Дженерики (англ. generics) позволяют создавать компоненты, которые совместимы с большим количеством типов, а не только с одним. Это делает компоненты более «открытыми».

Возможно у вас возникнет вопрос: а почему бы не использовать тип any для взятия сразу нескольких типов? Рассмотрев пример ниже, можно это легко понять.

Допустим, нужно создать какую-нибудь функцию, которая возвращает переданный ей параметр:

function dummyFun(arg: any): any {
 return arg;
}

Хоть any и является обобщающим типом, у него есть одно отличие. При использовании типа any у вас не получится узнать оригинальный тип передаваемой переменной.

Ниже приведён пример того, как можно это реализовать с помощью дженерика:

function dummyFun(arg: T): T {
 return arg
}

В этом коде используется generic-параметр T, тип которого можно будет захватить и в дальнейшем использовать.

Для более детального понимания generic-типов загляните в статью Generics and overloads от Charly Poly.

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

Модификаторы доступа управляют доступностью членов класса. TypeScript поддерживает 3 модификатора: public, private и protected.

public

Элементы с этим модификатором доступны отовсюду без каких-либо ограничений. Этот модификатор установлен по умолчанию.

private

Элементы с этим модификатором доступны только из того класса, в котором они определены.

protected

Элементы с этим модификатором доступны из класса, в котором они определены, и в подклассах/производных классах.

TSLINT

TSLINT — это стандарт, помогающий сохранять код более чистым, поддерживаемым и читаемым. Его можно настраивать, добавляя и изменяя правила и конфигурации.

Установка

Установить TSLINT можно как локально, так и глобально:

npm install tslint typescript --save-dev npm install tslint typescript -g

Потом идёт инициализация его в вашем проекте:

tslint --init

После этого у вас появится файл tslins.json, в который можно будет вносить собственные правила.

Конфигурирование

TSLINT позволяет задавать собственные правила касаемо того, как должен выглядеть ваш код. По умолчанию файл содержит основные правила и выглядит примерно так:

{
"defaultSeverity": "error",
"extends": [
 "tslint:recommended"
],
"jsRules": {},
"rules": {},
"rulesDirectory": []
}

Добавлять правила нужно в объект rules:

"rules": {
 "no-unnecessary-type-assertion": true,
 "array-type": [true, "array"],
 "no-double-space": true,
 "no-var-keyword": true,
 "semicolon": [true, "always", "ignore-bound-class-methods"]
},

Просмотреть все доступные правила можно в официальной документации.

Не смешно? А здесь смешно: @ithumor