Небольшая заметка о TypeScript и его возможностях.
Что такое TypeScript?
TypeScript — это супер набор JavaScript.
Почему TypeScript:
- TypeScript уменьшает количество ошибок, используя типы, которые помогают выявлять ошибки во время компиляции, а не во время выполнения;
- TypeScript позволяет вам использовать новый JS уже сегодня;
Типы TypeScript
TypeScript использует типы для анализа вашего кода на наличие ошибок и для понимания того, какие значения связаны с переменными.
С помощью TypeScript вы можете явно указывать типы для идентификаторов, таких как переменные, функции, объекты и т. д. Это называется —аннотации типов. Просто используйте синтаксис : type
// e.g. let someNumber: number; let someString: string; let someBoolean: boolean = true; someNumber = 1; someString = 1; // Compile error
Если вы забудете явно указать тип, то TypeScript выведет тип, который вы указали. Это называется вывод типа. Давайте посмотрим, например:
let someNumber = 0; // It is equivalent to the line below let someNumber: number = 0;
Когда мне нужно использовать вывод типа или аннотации типов?
Рекомендуется использовать аннотации типов, когда:
- Когда вы создаете переменную и позже присваиваете ей значение;
- Когда вам нужна переменная, которую нельзя вывести;
- Когда функция возвращает тип
any
и вам нужно указать значение;
Во всех остальных случаях следует максимально использовать вывод типов.
Есть две категории типов
Примитивные типы:
- строка — представляет собой текстовые данные;
// string examples let someString1: string = 'Hello World'; // single quotes let someString2: string = "Hello World"l // double quotes let someString3 = `Hello World`; // multi-line string with backtick let someString4 = `${someString1}`; // string interpolation
- число — представляет числовые значения;
// decimal numbers let someNumber1: number; let someNumber2 = 5.55; let x: number = 10, y: number = 20; // binary numbers let someBinaryNumber1 = 0b99; let someBinaryNumber2: number = 0b100; // big int let someBigIntNumber = 9007199254740991n; // hexadecimal numbers let someHexDecimalNumber = 0XA;
- boolean — имеет значения
true
илиfalse
let someBoolean1 = true; let someBoolean2: boolean; someBoolean2 = true;
- null — имеет только одно значение:
null
- undefined — имеет только одно значение:
undefined
. Также это значение по умолчанию для неинициализированной переменной - символ — представляет собой уникальную константу
Типы объектов:
- объект — представляет все значения, которые не относятся к примитивным типам.
let person: object; person = { name: 'Stas', age: 30 };
Мы можем явно указать свойства объекта person
с помощью приведенного ниже синтаксиса.
let person: { name: string, age: number }; person = { name: 'Stas', age: 30 }; // or we can do it in this way let person: { name: string, age: number } = { name: 'Stas', age: 30 };
Если вы попытаетесь получить доступ к свойству, которого нет в объекте person
, вы получите сообщение об ошибке.
- массив — это упорядоченный список данных
let someArray: number[]; // to add element we can use next syntax someArray.push(1); // or someArray[0] = 1;
Мы можем объявить массив с начальными элементами, и TypeScript выведет массив fruits
как массив строк.
let fruits = ['banana', 'mango'];
Это эквивалентно следующему:
let fruits: string[]; fruits = ['banana', 'mango'];
Конечно, вы не можете добавить какой-либо другой тип к объявленному типу массива. Компилятор TS выдаст ошибку.
fruits.push(100); // error
- enum — список именованных констант.
enum Week { Monday, // 0 Tuesday, // 1 Wednesday, // ... Thursday, Friday, Saturday, Sunday } // you can change start number explicitly enum Week { Monday = 1, ... }
Рекомендуется использовать перечисления, когда у вас есть небольшой набор фиксированных значений, которые тесно связаны и известны во время компиляции.
- функции — это строительные блоки читаемого, поддерживаемого и многократно используемого кода.
function functionName(par1: type, par2: type, ...): returnType { //some code }
Вы также можете объявить тип функции, который имеет параметры и тип возвращаемого значения.
(par: type, par,...) => returnType // example let stringConcat: (str1: string, str2: string) => string; stringConcat = function(str1: string, str2: string) { return str1 + str2; } // or you can do it like this let stringConcat: (str1: string, str2: string) => string = function(s1: string, s2: string) { return s1 + s2; } }
Функции могут иметь необязательные параметры
// use '?' after parameter name function functionName(par1: type, par2?: type): returnType { //some code }
Или параметры по умолчанию
// use 'par:type = value' syntax function functionName(par1: type, par2: type = 'Hello'): returnType { //some code }
Вы также можете перегрузить функцию
function add(a: number, b: number): number; function add(a: string, b: string): string; function add(a: any, b: any): any { return a + b; }
- class — в JS нет класса, но TS позволяет создать класс (как в C#, Java, C++) на основе наследования прототипов.
class Person { name; age; constructor(name, age) { this.name = name; this.age = age; } getPersonInfo() { return `${name} ${age}`; } } let person = new Person('Stas', 30); console.log(person.getPersonInfo());
Или вы можете использовать аннотации типа для свойств и методов в классе
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } getPersonInfo(): string { return `${name} ${age}`; } } let person = new Person('Stas', 30); console.log(person.getPersonInfo());
Класс TS имеет три модификатора доступа к свойствам и методам:
private
modifier — разрешает доступ к свойству в пределах того же класса.protected
modifier — разрешает доступ к свойству в пределах того же класса и подклассов.public
modifier — разрешает доступ к свойству из любого места.
В классе вы можете использовать модификатор readonly
, который позволяет помечать свойства как неизменяемые. С помощью readonly
вы можете присвоить значение только в объявлении свойства или конструкторе.
class Person { readonly name: string; constructor(name: string) { this.name = name; } }
Если вы хотите наследовать один класс от другого класса, используйте ключевое слово extends
, и если у родительского класса есть конструктор, который инициализирует некоторые свойства (имя, возраст), вам необходимо инициализировать эти свойства в конструкторе класса Employee, вызвав его родительский класс. конструктор через super()
.
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } getPersonInfo(): string { return `${name} ${age}`; } } class Employee extends Person { jobTitle: string; constructor( name: string, age: number, jobTitle: string) { super(name, age); this.jobTitle = jobTitle; } describe() { return super.getPersonInfo() + this.jobTitle; } } let employee = new Employee('Stas', 30, 'Developer'); console.log(employee.describe());
Класс может иметь статические свойства и методы, общие для всех экземпляров класса. Используйте static
keyword для объявления статического свойства или метода.
class Person { static counter: number; constructor() { Person.counter++; } static getPersonCount() { return Person.counter; } } let person1 = new Person(); let person2 = new Person(); console.log(Person.getPersonCount()); // 2
Еще одна хорошая особенность TypeScript — абстрактный класс, абстрактный класс имеет как минимум один абстрактный метод, и для его использования вам нужно его наследовать и предоставить реализацию для абстрактных методов.
Конечно, вы не можете создать экземпляр абстрактного класса.
abstract class Person { constructor(name: string, age: number) {} abstract getInfo(): string; } class Manager extends Employee { constructor(name: string, age: number) { super(name, age); } getInfo(): string { return this.name + ' ' + this.age; } }
- интерфейс — определите контракты в коде и предоставьте явные имена для проверки типов. Интерфейс может содержать только для чтения или необязательные свойства, и вы можете использовать интерфейс как тип класса, который заключает контракт между несвязанными классами или как типы функций.
interface Person { name: string, age: number } function getInfo(person: Person) { return person.name + ' ' + person.age; } // optional properties interface Person { name: string, age: number, jobTitle?: string } function getInfo(person: Person) { if(person.jobTitle) { return person.name + ' ' + person.age + '-' person.jobTitle; } return person.name + ' ' + person.age; } //readonly properties interface Person { readonly name: string, age: number } let person: Person; person = { name: 'Stas', age: 30 } person.name = 'David'; // error person.age = 31; // ok
Через интерфейс вы можете описать типы функций
interface StringFormat { (str: string, isLower: boolean): string } let format: StringFormat; format = function(str: string, isLower: boolean) { return isLower ? str.toLocaleLowerCase() : str.toLocaleUpperCase() } console.log(format('Hello World', true)); // hello world
Наконец, класс может реализовать интерфейс через implements
interface Json {
toJson(): string
}
class Employee implements Person {
name: string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
toJson() {
return JSON.stringify(this);
}
}
let employee = new Employee('Stas', 30);
console.log(employee.toJson()); // {"name":"Stas","age":"30"}
Последнее, что касается интерфейсов — он может расширять один или несколько существующих интерфейсов.
interface A { a(): void } interface B extends A { b(): void } // B interface has two methods: a() and b()
Особые типы
- any — используйте, когда вы не знаете тип на момент написания программы (некоторые значения могут исходить из API) или при миграции с JS на TS.
- void — используйте, когда функция не возвращает никакого значения.
function(message: string): void { console.log(message); }
- никогда — тип, который не содержит значения, это означает, что вы не можете присвоить какое-либо значение переменной с типом
never
.never
используйте, когда у вас есть функция, которая всегда выдает ошибку. Например:
function someFunction(message: string): never { throw new Error(message); }
Или вы можете использовать never
type, если у вас неопределенный цикл
let infinityLoop = function createInfinityLoop() { while(true) { console.log('.'); } }
- union — позволяет хранить в переменной значение одного или нескольких типов
let someUnionValue: string | number; someUnionValue = 1; // ok someUnionValue = 'Hello'; // ok someUnionValue = false; // error
- псевдонимы — позволяет вам определять новые имена для существующих типов
type newAliasesType = string | number; let someVariable: newAliasesType;
- literal — позволяет определить тип, который принимает только определенные строковые значения.
let statusLiteral: 'approve' | 'reject' | 'pending'; let myStatus: statusLiteral; myStatus = 'approve'; // ok myStatus = 'hello'; // error
- пересечение — создает новый тип путем объединения нескольких существующих типов, и новый тип будет иметь все функции существующих типов. Порядок не имеет значения
type typeAB = typeA & typeB; // definition // example type hybridVariable = number & string; let someVariable: hybridVariable = '3'; // ok someVariable = 3; // ok someVariable = false // error
Обратите внимание, что union
type и intersection
не совпадают.
Типовая защита и приведение
- используйте
typeof
, если вам нужно проверить тип переменной
let a: number; if (typeof a === 'number') { ... }
2. используйте instanceof
для проверки экземпляра класса
class Person1 {...} class Person2 {...} type Person = Person1 | Person2; function showInfo(person: Person) { if (person instanceof Person1) {...} if (person instanceof Person2) {...} }
3. используйте in
, если вам нужно проверить наличие свойства у объекта
class Person { name: string; ... } function showInfo(person: Person) { if('name' in person) {...} }
TS позволяют преобразовать переменную из одного типа в другой. Используйте ключевое слово as
или оператор <>
для приведения типов.
let a: typeA; let b = a as typeB; // or let a: typeA; let b = <typeB>a;
Общие
Обобщения позволяют вам писать повторно используемые и обобщенные формы функций, классов и интерфейсов.
Преимущества дженериков:
- Избавьтесь от приведения типов
- Реализация алгоритмов дженериков
- Проверка типа кредитного плеча во время компиляции.
function getRandomElement<T>(items: T[]): T { let randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex]; } let numbers = [1, 5, 7, 4, 2, 9]; let randomNumberEle = getRandomElement<number>(numbers); console.log(randomNumberEle); let strings = ['a', 'b', 'c']; let randomStringEle = getRandomElement<string>(strings); console.log(randomStringEle);
Вы можете использовать дженерики с несколькими типами
function merge<U, V>(obj1: U, obj2: V) { return { ...obj1, ...obj2 }; }
И обозначьте ограничение, вы используете ключевое слово extends
function merge<U extends object, V extends object>(obj1: U, obj2: V) {...}
Вдохновлено — https://www.typescripttutorial.net
Узнали что-то новое?
Если вам понравилась эта статья, вы можете купить мне чашечку кофе, и я выпью ее, когда буду писать следующую статью :)