
Всем привет! Меня зовут Лихопой Кирилл, я работаю 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, }
Наследование с помощью объединения:
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()); // Денис
На этом третья часть цикла подходит к концу, жду вас в следующей статье, в которой будут рассмотрены такие понятия, как литералы, дженерики и еще несколько тем