TypeScript в деталях
Спасибо Денису Улесову за помощь в редактировании материала.
Обратите внимание: предполагается, что вы имеете некоторый опыт работы с TypeScript
. Если нет, рекомендую начать с:
T
, K
и V
в дженериках / Generics
T
называется параметром общего типа (generic type parameter). Это заменитель (placeholder) настоящего (actual) типа, передаваемого функции.
Суть такая: берем тип, определенный пользователем, и привязываем (chain) его к типу параметра функции и типу возвращаемого функцией значения.
Так что все-таки означает T
? T
означает тип (type). На самом деле, вместо T
можно использовать любое валидное название. Часто в сочетании с T
используются такие общие переменные, как K
, V
, E
и др.
K
представляет тип ключа объекта;V
представляет тип значения объекта;E
представляет тип элемента.
Разумеется, мы не ограничены одним параметром типа - их может быть сколько угодно:
При вызове функции identity
можно явно определить действительный тип параметра типа. Или можно позволить TypeScript
самостоятельно сделать вывод относительного него:
Утилиты типа / Utility types
Утилиты типа (utility types) позволяют легко конвертировать, извлекать, исключать типы, получать параметры типов и типы значений, возвращаемых функциям и.
1. Partial<Type>
Данная утилита делает все свойства Type
опциональными (необязательными):
/**
* Make all properties in T optional.
* typescript/lib/lib.es5.d.ts
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
2. Required<Type>
Данная утилита делает все свойства Type
обязательными (она является противоположностью утилиты Partial
):
/**
* Make all properties in T required.
* typescript/lib/lib.es5.d.ts
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
3. Readonly<Type>
Данная утилита делает все свойства Type
доступными только для чтения (readonly
). Такие свойства являются иммутабельными (их значения нельзя изменять):
/**
* Make all properties in T readonly.
* typescript/lib/lib.es5.d.ts
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
4. Record<Keys, Type>
Данная утилита создает новый объектный тип (object type), ключами которого являются Keys
, а значениями свойств - Type
. Эта утилита может использоваться для сопоставления свойств одного типа с другим типом:
/**
* Construct a type with a set of properties K of type T.
* typescript/lib/lib.es5.d.ts
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
5. Exclude<UnionType, ExcludedMembers>
Данная утилита создает новый тип посредством исключения из UnionType
всех членов объединения, которые могут быть присвоены (assignable) ExcludedMembers
:
/**
* Exclude from T those types that are assignable to U.
* typescript/lib/lib.es5.d.ts
*/
type Exclude<T, U> = T extends U ? never : T;
6. Extract<Type, Union>
Данная утилита создает новый тип посредством извлечения из Type
всех членов объединения, которые могут быть присвоены Union
:
/**
* Extract from T those types that are assignable to U.
* typescript/lib/lib.es5.d.ts
*/
type Extract<T, U> = T extends U ? T : never;
7. Pick<Type, Keys>
Данная утилита создает новый тип посредством извлечения из Type
набора (множества) свойств Keys
(Keys
- строковый литерал или их объединение):
/**
* From T, pick a set of properties whose keys are in the union K.
* typescript/lib/lib.es5.d.ts
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
8. Omit<Type, Keys>
Данная утилита создает новый тип посредством исключения из Type
набора свойств Keys
(Keys
- строковый литерал или их объединение) (она является противоположностью утилиты Pick
):
/**
* Construct a type with the properties of T except for those
* in type K.
* typescript/lib/lib.es5.d.ts
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
9. NonNullable<Type>
Данная утилита создает новый тип посредством исключения из Type
значений null
и undefined
:
/**
* Exclude null and undefined from T.
* typescript/lib/lib.es5.d.ts
*/
type NonNullable<T> = T extends null | undefined ? never : T;
10. Parameters<Type>
Данная утилита создает кортеж (tuple) из типов параметров фу нкции Type
:
/**
* Obtain the parameters of a function type in a tuple.
* typescript/lib/lib.es5.d.ts
*/
type Parameters<T extends (...args: any) => any> = T extends
(...args: infer P) => any ? P : never;
11. ReturnType<Type>
Данная утилита извлекает тип значения, возвращаемого функцией Type
:
/**
* Obtain the return type of a function type.
* typescript/lib/lib.es5.d.ts
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
12. Uppercase<StringType>
Данная утилита конвертирует строковый литеральный тип в верхний регистр:
13. Lowercase<StringType>
Данная утилита конвертирует строковый литеральный тип в нижний регистр:
14. Capitalize<StringType>
Данная утилита конвертирует первый символ строкового литерального типа в верхний регистр:
15. Uncapitalize<StringType>
Данная утилита конвертирует первый символ строкового литерального типа в нижний регистр:
Кроме описанных выше, существует еще несколько встроенных утилит типа:
ConstructorParameters<Type>
: создает кортеж или массив из конструктора функции (речь во всех случаях идет о типах). Результатом является кортеж всех параметров типа (или типnever
, еслиType
не является функцией);InstanceType<Type>
: создает тип, состоящий из типа экземпляра конструктора функции типаType
:ThisParameterType<Type>
: извлекает тип из параметраthis
функции. Если функция не имеет такого параметра, возвращаетсяunknown
.
Классы / Classes
В объектно-ориентированных языках программирования класс - это шаблон (blueprint - проект, схема), описывающий свойства и методы, которые являются общими для всех объектов, создаваемых с помощью класса.
1. Свойства и методы
1.1. Свойства экземпляров и статические свойства
В TS
, как и в JS
, класс определяется с помощью ключевого слова class
:
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
В приведенном примере определяется класс User
с одним свойством экземпляров name
. В действительности, класс - это синтаксический сахар для функции-конструктора. Если установить результат компиляции в ES5
, то будет сгенерирован следующий код:
"use strict";
var User = /** @class */ (function () {
function User(name) {
this.name = name;
}
return User;
}());
Кроме свойств экземпляров, в классе могут определяться статические свойства. Такие свойства определяются с помощью ключевого слова static
:
class User {
static cid: string = "eft";
name: string;
constructor(name: string) {
this.name = name;
}
}
В чем разница между свойствами экземпляров и статическими свойствами? Посмотрим на компилируемый код:
"use strict";
var User = /** @class */ (function () {
function User(name) {
this.name = name;
}
User.cid = "eft";
return User;
}());
Как видим, свойства экземпляров определяются в экземпляре класса, а статические свойства - в его конструкторе.
1.2. Методы экземпляров и статические методы
Кроме свойств, в классе могут определяться методы экземпляров и статические методы:
class User {
static cid: string = "eft";
name: string;
constructor(name: string) {
this.name = name;
}
static printCid() {
console.log(User.cid);
}
send(msg: string) {
console.log(`${this.name} send a message: ${msg}`);
}
}
В чем разница между методами экземпляров и статическими методами? Посмотрим на компилируемый код:
"use strict";
var User = /** @class */ (function () {
function User(name) {
this.name = name;
}
User.printCid = function () {
console.log(User.cid);
};
User.prototype.send = function (msg) {
console.log("".concat(this.name, " send a message: ").concat(msg));
};
User.cid = "eft";
return User;
}());
Как видим, методы экземпляров добавляются в прототип конструктора, а статические методы в сам конструктор.
2. Аксессоры
В классе могут определяться так называемые аксессоры (accessors). Аксессоры, которые делятся на геттеры (getters) и сеттеры (setters) могут использоваться, например, для инкапсуляции данных или их верификации:
class User {
private _age: number = 0;
get age(): number {
return this._age;
}
set age(value: number) {
if (value > 0 && value <= 120) {
this._age = value;
} else {
throw new Error("The set age value is invalid!");
}
}
}
3. Наследование
Наследование (inheritance) - это иерархическая модель для связывания классов между собой. Наследование - это возможность класса наследовать функционал другого класса и расширять его новым функционалом. Наследование - это наиболее распространенный вид отношений между классами, между классами и интерфейсами, а также между интерфейсами. Наследование облегчает повторное использование кода.
Наследование реализуется с помощью ключевого слова extends
. Расширяемый класс называется базовым (base), а расширяющий - производным (derived). Производный класс содержит все свойства и методы базового и может определять дополнительные члены.