Всем привет! Меня зовут Лихопой Кирилл, я работаю fullstack-разработчиком. Это - уже третья часть руководства по TypeScript для начинающих, в которой мы разберем более сложные темы, такие как классы, модули и интерфейсы.
Предыдущие части:
Часть 1 - введение и примитивные типы данных
Часть 2 - ссылочные типы данных
Часть 4 - Литералы и дженерики
Классы в TypeScript
Мы можем определить типы для любых данных, которые будут в классе:
class Person {
name: string;
isCool: boolean;
friends: number;
constructor(n: string, c: boolean, f: number) {
this.name = n;
this.isCool = c;
this.friends = f;
}
sayHello() {
return `Привет, меня зовут ${this.name}, у меня есть ${this.friends} друзей`
}
}
const person1 = new Person('Денис', false, 10);
const person2 = new Person('Вова', 'да', 6); // ОШИБКА: Аргумент типа 'string' не может быть присвоен параметру с типом 'boolean'
console.log(person1.sayHello()); // Привет, меня зовут Денис, у меня есть 10 друзей
Затем мы можем создать массив people
, в котором содержаться экземпляры класса Person
:
let People: Person[] = [person1, person2];
Мы можем добавить модификаторы доступа к свойствам класса. TypeScript также предоставляет новый модификатора доступа readonly
(мы говорили о нем в прошлой части):
class Person {
readonly name: string; // это свойство неизменно - его можно только прочитать
private isCool: boolean; // можно прочитать и изменять только в пределах этого класса
protected email: string; // можно прочитать и изменить только из класса и наследуемых от него
public friends: number; // можно прочитать и изменить откуда угодно, даже вне класса
constructor(n: string, c: boolean, e: string, f: number) {
this.name = n;
this.isCool = c;
this.email = e;
this.friends = f;
}
sayMyName() {
console.log(`Ты не Хайзенберг, ты ${this.name}`);
}
}
const person1 = new Person('Менделеев', false, 'men@de.ru', 118);
console.log(person.name); // все в порядке
person1.name = 'Хайзенберг'; // ОШИБКА: только для чтения
console.log(person1.isCool); // ОШИБКА: private свойство - доступ есть только в пределах класса Person
console.log(person1.email); // ОШИБКА: protected свойство - доступ есть только в пределах класса Person и его наследниках
console.log(person1.friends); // public свойство - никаких проблем
Мы можем сделать наш код более лаконичным, если опишем свойства класса следующим образом:
class Person {
constructor(
readonly name: string,
private isCool: boolean,
protected email: string,
public friends: number
){}
sayMyName() {
console.log(`Ты не Хайзенберг, ты ${this.name}`);
}
}
const person1 = new Person('Менделеев', false, 'men@de.ru', 118);
console.log(person.name); // Менделеев
Когда мы описываем класс таким образом, свойства назначаются автоматически прямо в конструкторе, спасая нас от их повторного написания.
Обратите внимание - когда мы пропускаем модификатор доступа, то по умолчанию свойство будет public
.
Классы могут наследоваться, так же, как и в обычном JavaScript:
class Programmer extends Person {
programmingLanguages: string[];
constructor(
name: string,
isCool: boolean,
email: string,
friends: number,
pL: string[]
){
// Вызов super должен содержать все параметры базового класса (Person), т.к. конструктор не наследуется
super(name, isCool, email, friends);
this.programmingLanguages = pl;
}
}
Больше о классах вы можете узнать на официальном сайте TypeScript.
Модули в TypeScript
В JavaScript модули - это просто файлы, которые содержат связанный код. Функционал может быть импортирован и экспортирован между модулями, чтобы сохранять код в организованном виде.
TypeScript так же имеет поддержку модулей. Файлы TypeScript будут компилироваться в отдельные JavaScript файлы.
Измените следующие параметры в файле tsconfig.json
, чтобы добавить поддержку современных экспорта и импорта:
"target": "es2016",
"module": "es2015"
(Несмотря на то, что для Node-проекта вам, вероятно, очень хочется добавить "module":"CommonJS"
, это не сработает, т.к. Node еще не поддерживает современные экспорт и импорт.)
Теперь, добавьте атрибут type="module"
в тег скрипта в вашем HTML-файле:
<script type="module" src="/public/script.js"></script>
Теперь мы можем импортировать и экспортировать файлы с помощью ES6:
// src/hello.ts
export function sayHi() {
console.log('Всем привет!');
}
// src/script.ts
import { sayHi } from './hello.js';
syaHi(); // Всем привет!
Обратите внимание: всегда импортируйте файл с разрешением .js
, даже если это TypeScript-файл.
Интерфейсы в TypeScript
Интерфейсы объявляются как объекты и выглядят следующим образом:
interface Person {
name: string;
age: number;
}
function sayHi(person: Person) {
console.log(Привет, ${person.name});
}
sayHi({
name: 'Джон',
age: 33,
}); // Привет, Джон
Вы также можете объявлять их как объекты, используя type
(псевдоним типа):
type Person = {
name: string;
age: number;
}
function sayHi(person: Person) {
console.log(Привет, ${person.name});
}
sayHi({
name: 'Джон',
age: 33,
}); // Привет, Джон
А еще тип объекта может быть указан анонимно, прямо в параметрах функции:
function sayHi(person: { name: string; age: number }) {
console.log(`Привет, ${person.name}`);
}
sayHi({
name: 'Джон',
age: 33,
}); // Привет, Джон
Интерфейсы очень похожи на псевдонимы типов, и во многих случаях вы можете использовать их обоих. Их ключевое различие - псевдонимы нельзя наследовать, а интерфейсы можно.
Следующие примеры взяты из документации TypeScript.
Наследование интерфейса:
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear: Bear = {
name: 'Винни',
honey: false,
}
Изучение TypeScript - полное руководство для начинающих. Часть 3 - Классы и интерфейсы. типов:
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = {
name: 'Винни',
honey: true,
}
Добавление новых полей к существующему интерфейсу:
interface Animal {
name: string
}
// Добавление поля к интерфейсу
interface Animal {
tail: boolean
}
const dog: Animal = {
name: 'Хатико',
tail: true,
}
А вот и разница: типы не могут изменены после объявления:
type Animal = {
name: string
}
type Animal = {
tail: boolean
}
// ОШИБКА: Дублирующийся идентификатор 'Animal'.
Документация TypeScript советует использовать интерфейсы для объявления объектов, если вам не требуется использовать возможности типов.
Также в интерфейсе можно указать типы данных для параметров функции и значения, которое она возвращает:
interface Person {
name: string
age: number
speak(sentence: string): void
}
const person1: Person = {
name: 'Джон',
age: 33,
speak: sentence => console.log(sentence),
}
У вас мог возникнуть вопрос: почему мы используем интерфейсы вместо классов в примере выше?
Во-первых, интерфейсы используются только в TypeScript, не в JavaScript. Это значит, что они не будут скомпилированы, и соответственно не будут раздувать наш JavaScript-файл. А так как классы используются в JavaScript, то они будут скомпилированы.
Во-вторых, класс это, по сути своей, фабрика объектов (то есть это “план” того, как должен выглядеть объект), в то время как интерфейсы используются исключительно для проверки типов.
Класс может инициализировать свойства и методы, чтобы помочь создавать объекты, а интерфейсы просто описывают, какие свойства должен иметь объект и каких типов они будут.
Интерфейсы с классами
Мы можем указать классу, что он должен содержать определенные свойства и методы путем реализации интерфейса:
interface HasFormatter {
format(): string;
}
class Person implements HasFormatter {
constructor(public username: string, protected password: string) {}
format() {
return this.username.toLocaleLowerCase();
}
}
// Должно быть объектом, который реализует интерфейс HasFormatter
let person1: HasFormatter;
let person2: HasFormatter;
person1 = new Person('Денис', 'password123');
person2 = new Person('Женя', 'TypeScripter999');
console.log(person1.format()); // Денис
На этом третья часть цикла подходит к концу, жду вас в следующей статье, в которой будут рассмотрены такие понятия, как литералы, дженерики и еще несколько тем.
P.S. Автор искренне извиняется за большой перерыв между второй и третью частями, который связан с огромными переменами в жизни (в т.ч. релокейт в Казахстан)