Pull to refresh

Изучение TypeScript — полное руководство для начинающих. Часть 3 — Классы и интерфейсы

Reading time5 min
Views15K
Original author: Danny Adams

Всем привет! Меня зовут Лихопой Кирилл, я работаю fullstack-разработчиком. Это - уже третья часть руководства по TypeScript для начинающих, в которой мы разберем более сложные темы, такие как классы, модули и интерфейсы.

Предыдущие части:
Часть 1 - введение и примитивные типы данных
Часть 2 - ссылочные типы данных
Часть 4 - Литералы и дженерики
Часть 5 - Строгий режим и сужение типов

Классы в 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. Автор искренне извиняется за большой перерыв между второй и третью частями, который связан с огромными переменами в жизни (в т.ч. релокейт в Казахстан)

Tags:
Hubs:
Total votes 6: ↑4 and ↓2+2
Comments0

Articles