Первое использование АпогеяTypeScriptЭто было в проекте Angular 2.x, до того, как TypeScript стал известен общественности. Однако сейчас все больше и больше друзей изучают TypeScript.В этой статье брат Абао начнёт с16Давайте начнем с каждого аспекта и шаг за шагом познакомим вас с TypeScript. Заинтересованные друзья не должны пропустить это.
1. Что такое TypeScript
TypeScript— это бесплатный язык программирования с открытым исходным кодом, разработанный Microsoft. Это расширенный набор JavaScript, который, по сути, добавляет к языку необязательную статическую типизацию и объектно-ориентированное программирование на основе классов.
TypeScript предоставляет новейшие и развивающиеся функции JavaScript, в том числе из ECMAScript 2015 и будущих предложений, таких как асинхронные функции и декораторы, для помощи в создании надежных компонентов. На следующей диаграмме показана связь между TypeScript и ES5, ES2015 и ES2016:
1.1 Различия между TypeScript и JavaScript
TypeScript | JavaScript |
---|---|
Расширенный набор JavaScript для решения проблемы сложности кода больших проектов. | Язык сценариев для создания динамических веб-страниц. |
Ошибки могут быть найдены и исправлены во время компиляции | Поскольку это интерпретируемый язык, ошибки можно найти только во время выполнения. |
Строгая типизация, поддерживает статическую и динамическую типизацию | Слабая типизация, нет опции статической типизации |
в конечном итоге компилируется в код JavaScript, понятный браузеру | Можно использовать прямо в браузере |
Поддержка модулей, дженериков и интерфейсов | Нет поддержки модулей, дженериков или интерфейсов. |
Поддержка сообщества все еще растет, и она еще не огромна | Много поддержки сообщества и много документации и поддержки решения проблем |
1.2 Получить TypeScript
Можно использовать компилятор TypeScript из командной строкиnpmменеджер пакетов для установки.
1. Установите TypeScript
$ npm install -g typescript
2. Проверьте TypeScript
$ tsc -v
# Version 4.0.2
3. Скомпилируйте файл TypeScript
$ tsc helloworld.ts
# helloworld.ts => helloworld.js
Конечно, для тех, кто только начинает работать с TypeScript, устанавливать его не нужно.typescript
, но используйте онлайнTypeScript Playgroundдля изучения нового синтаксиса или новых функций. по конфигурацииTS ConfigTarget, вы можете установить разные цели компиляции, чтобы компилировать и генерировать разные целевые коды.
Целью компиляции, установленной в приведенном ниже примере, является ES5:
(Источник изображения:woohoo.typescriptwolf.org/play)
1.3 Типичный рабочий процесс TypeScript
Как видите, на изображении выше есть 3 файла ts: a.ts, b.ts и c.ts. Эти файлы будут скомпилированы компилятором TypeScript в 3 файла js, а именно a.js, b.js и c.js, в соответствии с настроенными параметрами компиляции. Для большинства веб-проектов, разработанных с помощью TypeScript, мы также упаковываем скомпилированные файлы js, а затем развертываем их.
1.4 Первый опыт TypeScript
создать новыйhello.ts
файл и введите следующее:
function greet(person: string) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
затем выполнитьtsc hello.ts
команда, то будет сгенерирован скомпилированный файлhello.js
:
"use strict";
function greet(person) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
Глядя на приведенный выше скомпилированный вывод, мы обнаруживаем, чтоperson
Информация о типе параметра стирается после компиляции. TypeScript будет статически проверять тип только на этапе компиляции, и если будет обнаружена ошибка, об ошибке будет сообщено во время компиляции. Во время выполнения скомпилированный JS ничем не отличается от обычного файла JavaScript и не будет проверяться на тип.
2. Основные типы TypeScript
2.1 Логический тип
let isDone: boolean = false;
// ES5:var isDone = false;
2.2 Тип номера
let count: number = 10;
// ES5:var count = 10;
2.3 Тип строки
let name: string = "semliker";
// ES5:var name = 'semlinker';
2.4 Тип символа
const sym = Symbol();
let obj = {
[sym]: "semlinker",
};
console.log(obj[sym]); // semlinker
2.5 Типы массивов
let list: number[] = [1, 2, 3];
// ES5:var list = [1,2,3];
let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法
// ES5:var list = [1,2,3];
2.6 Типы перечислений
Используя перечисления, мы можем определить некоторые константы с именами. Используйте перечисления, чтобы сформулировать намерение или создать отдельный набор вариантов использования. TypeScript поддерживает числовые и строковые перечисления.
1. Числовое перечисление
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
По умолчанию начальное значение NORTH равно 0, а остальные члены автоматически увеличиваются с 1. Другими словами, Направление.ЮГ имеет значение 1, Направление.ВОСТОК имеет значение 2, а Направление.ЗАПАД имеет значение 3.
После компиляции приведенного выше примера перечисления соответствующий код ES5 выглядит следующим образом:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["NORTH"] = 0)] = "NORTH";
Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
Direction[(Direction["EAST"] = 2)] = "EAST";
Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
Конечно, мы также можем установить начальное значение NORTH, например:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST,
}
2. Перечисление строк
В TypeScript 2.4 нам разрешено использовать строковые перечисления. В строковом перечислении каждый элемент должен быть инициализирован строковым литералом или другим элементом строкового перечисления.
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
Код ES5, соответствующий приведенному выше коду, выглядит следующим образом:
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
Наблюдая за результатами компиляции числового перечисления и строкового перечисления, мы можем знать, что в дополнение к поддержке числового перечисленияОт имени элемента к значению элементаВ дополнение к нормальному отображению , он также поддерживаетОт значения члена к имени членаОбратное отображение:
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0
Кроме того, для чисто строковых перечислений мы не можем пропустить какие-либо инициализаторы. Числовые перечисления инициализируются с использованием правил по умолчанию, если значение не задано явно.
3. Постоянное перечисление
Помимо нумерации чисел и нумерации строк, существует особый вид перечисления - перечисление констант. он используетconst
Перечисления с ключевыми словами, константные перечисления будут использовать встроенный синтаксис и не будут компилироваться и генерировать какой-либо JavaScript для типов перечислений. Чтобы лучше понять это предложение, давайте рассмотрим конкретный пример:
const enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
Код ES5, соответствующий приведенному выше коду, выглядит следующим образом:
"use strict";
var dir = 0 /* NORTH */;
4. Гетерогенная нумерация
Значения членов гетерогенного перечисления представляют собой смесь чисел и строк:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
Код ES5 для приведенного выше кода выглядит следующим образом:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
Наблюдая за сгенерированным выше кодом ES5, мы можем обнаружить, что числовое перечисление имеет больше «обратных отображений», чем строковое перечисление:
console.log(Enum.A) //输出:0
console.log(Enum[0]) // 输出:A
2.7 Любой тип
В TypeScript любой тип может быть классифицирован как любой тип. Это делает тип any типом верхнего уровня (также известным как глобальный супертип) системы типов.
let notSure: any = 666;
notSure = "semlinker";
notSure = false;
any
Типы, по сути, являются аварийным люком для системы типов. Это дает нам как разработчикам большую свободу: TypeScript позволяет намany
Значение типа выполняет любую операцию без предварительной проверки. Например:
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
Во многих сценариях это слишком свободно. использоватьany
типа, легко написать код, который правильно типизирован, но имеет проблемы во время выполнения. если мы используемany
тип, вы не можете использовать расширенные механизмы защиты, предоставляемые TypeScript. чтобы решитьany
Проблема, которую представил TypeScript 3.0unknown
Типы.
2.8 Неизвестные типы
как все типы могут быть назначеныany
, все типы также могут быть назначеныunknown
. Это делаетunknown
стать еще одним типом верхнего уровня системы типов TypeScript (еще одинany
). Давайте взглянемunknown
Пример использования типов:
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
правильноvalue
Все присваивания переменных считаются корректными по типу. Однако, когда мы пытаемся преобразовать тип вunknown
Что происходит, когда значение присваивается переменной другого типа?
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
unknown
Типы могут быть присвоены толькоany
тип иunknown
сам тип. Интуитивно это имеет смысл: хранить могут только контейнеры, способные хранить значения произвольных типов.unknown
значение типа. Ведь мы не знаем переменнуюvalue
Какой тип значения хранится в .
Теперь давайте посмотрим, когда мы попытаемсяunknown
Что происходит, когда операция выполняется над значением . Вот какими мы были раньшеany
Те же операции, что и в главе:
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
Будуvalue
Тип переменной установлен наunknown
После этого эти операции больше не считаются типокорректными. поставивany
тип изменен наunknown
Тип, мы изменили настройку по умолчанию «Разрешить все изменения» на «Запретить любые изменения».
2.9 Типы кортежей
Как мы все знаем, массивы обычно состоят из значений одного типа, но иногда нам нужно хранить значения разных типов в одной переменной, тогда мы можем использовать кортежи. В JavaScript нет кортежей, кортежи — это специфичные для TypeScript типы, которые работают как массивы.
Кортежи можно использовать для определения типов с ограниченным числом безымянных свойств. Каждое свойство имеет связанный тип. При использовании кортежей вы должны указать значение для каждого свойства. Чтобы более интуитивно понять концепцию кортежей, давайте рассмотрим конкретный пример:
let tupleType: [string, boolean];
tupleType = ["semlinker", true];
В приведенном выше коде мы определяемtupleType
переменная, тип которой является массивом типа[string, boolean]
, а затем мы по очереди инициализируем переменные tupleType с правильным типом. Как и в случае с массивами, мы можем получить доступ к элементам кортежа с помощью подписки:
console.log(tupleType[0]); // semlinker
console.log(tupleType[1]); // true
Когда кортеж инициализируется, если есть несоответствие типов, например:
tupleType = [true, "semlinker"];
В этот момент компилятор TypeScript выдаст следующее сообщение об ошибке:
[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.
Видимо, из-за несоответствия типов. При инициализации кортежа мы также должны предоставить значение каждого атрибута, иначе будут ошибки, такие как:
tupleType = ["semlinker"];
В этот момент компилятор TypeScript выдаст следующее сообщение об ошибке:
Property '1' is missing in type '[string]' but required in type '[string, boolean]'.
2.10 Пустой тип
В каком-то смысле тип void противоположен типу any, что означает отсутствие типа. Когда функция не возвращает значение, вы обычно видите возвращаемый тип void:
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}
Код ES5, сгенерированный путем компиляции приведенного выше кода, выглядит следующим образом:
"use strict";
function warnUser() {
console.log("This is my warning message");
}
Обратите внимание, что объявление переменной типа void не имеет никакого эффекта, потому что в строгом режиме ее значение может быть толькоundefined
:
let unusable: void = undefined;
2.11 Null и Undefined типы
В машинописном языке,undefined
а такжеnull
Оба имеют свои типыundefined
а такжеnull
.
let u: undefined = undefined;
let n: null = null;
2.12 объект, объекты и типы {}
1. тип объекта
Тип объекта: новый тип, представленный в TypeScript 2.2, который используется для представления непримитивных типов.
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
create(o: object | null): any;
// ...
}
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error
2. Тип объекта
Тип объекта: это тип всех экземпляров класса Object, который определяется следующими двумя интерфейсами:
- Интерфейс Object определяет свойства объекта-прототипа Object.prototype;
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
- Интерфейс ObjectConstructor определяет свойства класса Object.
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}
declare var Object: ObjectConstructor;
Все экземпляры класса Object наследуют все свойства интерфейса Object.
3. Тип {}
Тип {} описывает объект без членов. TypeScript сгенерирует ошибку времени компиляции, когда вы попытаетесь получить доступ к произвольным свойствам такого объекта.
// Type {}
const obj = {};
// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";
Однако вы по-прежнему можете использовать все свойства и методы, определенные для типа Object, которые неявно доступны через цепочку прототипов JavaScript:
// Type {}
const obj = {};
// "[object Object]"
obj.toString();
2.13 Никогда не печатайте
never
Типы представляют типы значений, которые никогда не существуют. Например,never
Типы — это возвращаемый тип тех функциональных выражений или выражений стрелочных функций, которые всегда выдают исключение или вообще никогда не возвращают значение.
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
В TypeScript функция «никогда не вводить» может использоваться для реализации комплексных проверок. Конкретный пример выглядит следующим образом:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
Обратите внимание, что внутри ветки else мы присваиваем foo, суженную до never, явно объявленной переменной never. Если все логически правильно, это должно скомпилироваться и пройти. Но предположим, что однажды ваш коллега изменит тип Foo:
type Foo = string | number | boolean;
Однако он забыл изменить в то же времяcontrolFlowAnalysisWithNever
Поток управления в методе, в это время тип foo ветви else будет сужен доboolean
type, так что его нельзя будет присвоить типу never, и будет сгенерирована ошибка компиляции. Таким образом, мы можем гарантировать, что
controlFlowAnalysisWithNever
методы всегда исчерпывают все возможные типы Foo . Из этого примера можно сделать вывод:Используйте never, чтобы избежать добавления нового типа объединения без соответствующей реализации, и цель состоит в том, чтобы написать код, который абсолютно безопасен для типов.
3. Утверждения TypeScript
3.1 Утверждение типа
Иногда вы столкнетесь с ситуацией, когда вы знаете больше деталей о значении, чем TypeScript. Обычно это происходит, когда вы четко знаете, что сущность имеет более точный тип, чем ее существующий тип.
Утверждения типов — это способ сказать компилятору: «Поверь мне, я знаю, что делаю». Утверждения типов аналогичны преобразованиям типов в других языках, но без специальной проверки данных и деструктурирования. Это не влияет на время выполнения, оно просто работает во время компиляции.
Утверждения типов бывают двух видов:
1. Синтаксис «угловых скобок»
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
2.как синтаксис
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
3.2 Ненулевое утверждение
Новый оператор постфиксного выражения в контексте, когда средство проверки типов не может определить тип!
Может использоваться для подтверждения того, что объект операции не имеет нулевого и неопределенного типа.В частности, x!исключает null и undefined из диапазона x.
Итак, что именно делает оператор ненулевого утверждения? Давайте сначала рассмотрим некоторые сценарии использования оператора ненулевого утверждения.
1. Игнорировать неопределенные и нулевые типы
function myFunc(maybeString: string | undefined | null) {
// Type 'string | null | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
2. Игнорировать неопределенные типы при вызове функций
type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}
потому что!
Оператор ненулевого утверждения будет удален из скомпилированного кода JavaScript, поэтому особое внимание следует уделить фактическому использованию. Например следующий пример:
const a: number | undefined = undefined;
const b: number = a!;
console.log(b);
Приведенный выше код TS будет скомпилирован для создания следующего кода ES5:
"use strict";
const a = undefined;
const b = a;
console.log(b);
Хотя в коде TS мы используем ненулевое утверждение, такое чтоconst b: number = a!;
Выражения можно проверить с помощью средства проверки типов TypeScript. Но в сгенерированном коде ES5!
Оператор ненулевого утверждения был удален, поэтому выполнение приведенного выше кода в браузере приведет к выводу в консоли.undefined
.
3.3 Утверждение детерминированного присваивания
Детерминированные утверждения присваивания были введены в TypeScript 2.7, что позволяет размещать!
номер, который сообщает TypeScript, что свойство будет назначено явно. Чтобы лучше понять, что он делает, давайте рассмотрим конкретный пример:
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
Очевидно, что сообщение об исключении заключается в том, что переменная x используется перед присваиванием.Чтобы исправить это, мы можем использовать детерминированное утверждение присваивания:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
пройти черезlet x!: number;
Определенное утверждение присваивания, компилятор TypeScript знает, что свойству будет определенно присвоено значение.
4. Типовая защита
Защита типа — это выражение, которое выполняет проверки во время выполнения, чтобы гарантировать, что тип находится в пределах определенного диапазона.Другими словами, защита типов гарантирует, что строка является строкой, хотя ее значение также может быть числом. Защита типа не полностью отличается от обнаружения функций, основная идея состоит в том, чтобы попытаться обнаружить свойство, метод или прототип, чтобы определить, как обрабатывать значение. В настоящее время существует четыре основных способа реализации защиты типа:
4.1 в ключевом слове
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
4.2 тип ключевого слова
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof
Охранники типов поддерживают только две формы:typeof v === "typename"
а такжеtypeof v !== typename
,"typename"
должно быть"number"
,"string"
,"boolean"
или"symbol"
. Но TypeyScript не мешает вам сравнивать с другими строками, язык не распознает эти выражения как охранников типа.
4.3 Ключевое слово instanceof
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
4.4 Предикаты типов для пользовательской защиты типов
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
5. Типы Union и псевдонимы типов
5.1 Типы союзов
Типы союзов обычно связаны сnull
илиundefined
использовать вместе:
const sayHello = (name: string | undefined) => {
/* ... */
};
Например, здесьname
Типstring | undefined
означает, чтоstring
илиundefined
значение, переданноеsayHello
функция.
sayHello("semlinker");
sayHello(undefined);
Из этого примера вы можете интуитивно понять, что объединение типов A и B — это тип, который принимает значения A и B. Кроме того, для типов объединения вы можете столкнуться со следующими вариантами использования:
let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';
в приведенном выше примере1
,2
или'click'
Называемый литеральным типом, он используется для ограничения значения только одним из нескольких значений.
5.2 Идентифицируемые союзы
Дискриминированные объединения TypeScript, также известные как алгебраические типы данных или помеченные типы объединений.Он содержит 3 основных пункта: идентифицируемые типы, типы объединения и охранники типов.
Суть этого типа заключается в методе защиты типа, который объединяет типы объединения и литеральные типы.Если тип является типом объединения нескольких типов, и несколько типов имеют общее свойство, то это общее свойство можно использовать для создания блоков защиты различных типов.
1. Узнаваемый
Различение требует, чтобы каждый элемент в типе объединения имел атрибут одноэлементного типа, например:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
В приведенном выше коде мы соответственно определяемMotorcycle
,Car
а такжеTruck
три интерфейса, каждый из которых содержитvType
свойства, которые называются узнаваемыми свойствами, в то время как другие свойства связаны только с интерфейсом функции.
2. Тип соединения
На основе трех интерфейсов, определенных выше, мы можем создатьVehicle
Тип союза:
type Vehicle = Motorcycle | Car | Truck;
Теперь мы можем начать использоватьVehicle
союзный тип, дляVehicle
Переменная типа, которая может представлять различные типы транспортных средств.
3. Типовая защита
Давайте определимevaluatePrice
метод, который используется для расчета цены исходя из типа, вместимости и коэффициента оценки транспортного средства, который реализуется следующим образом:
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
Для приведенного выше кода компилятор TypeScript выдаст следующее сообщение об ошибке:
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
Причина в том, что в интерфейсе Мотоцикла его нет.capacity
Атрибут, а для интерфейса Car его не существуетcapacity
Атрибуты. Итак, как мы должны решить вышеперечисленные проблемы сейчас? На этом этапе мы можем использовать защиту типов. Проведем рефакторинг ранее определенногоevaluatePrice
метод, рефакторинг кода выглядит следующим образом:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
return vehicle.make * EVALUATION_FACTOR;
}
}
В приведенном выше коде мы используемswitch
а такжеcase
оператор для реализации защиты типа, гарантируя, чтоevaluatePrice
метод, мы можем безопасно получить доступvehicle
Атрибуты, содержащиеся в объекте, используются для правильного расчета цены, соответствующей типу транспортного средства.
5.3 Псевдонимы типов
Псевдонимы типов используются для присвоения типу нового имени.
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
Шестерка, крестового типа
Кросс-типирование в TypeScript — это объединение нескольких типов в один тип. пройти через&
Операторы могут объединять несколько существующих типов в один тип, содержащий все требуемые свойства типа.
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
let point: Point = {
x: 1,
y: 1
}
В приведенном выше коде мы сначала определяемPartialPointX
введите, затем используйте&
оператор создает новыйPoint
тип, который представляет точку с координатами x и y, а затем определяетPoint
переменная типа и инициализирована.
6.1 Объединение свойств базового типа с одинаковыми именами
Итак, теперь возникает проблема, предположим, что в процессе слияния нескольких типов случается так, что некоторые типы имеют одинаковые члены, но соответствующие типы несовместимы, например:
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y;
type YX = Y & X;
let p: XY;
let q: YX;
В приведенном выше коде интерфейс X и интерфейс Y имеют один и тот же элемент c, но их типы не совпадают. В этом случае может ли тип члена c в типе XY или типе YX бытьstring
илиnumber
Что насчет типа? Например следующий пример:
p = { c: 6, d: "d", e: "e" };
q = { c: "c", d: "d", e: "e" };
Почему после смешивания интерфейса X и интерфейса Y тип члена c станетnever
Шерстяная ткань? Это связано с тем, что тип члена c после примесиstring & number
, то есть тип члена c может быть либоstring
Тип также может бытьnumber
Типы. Очевидно, такого типа не существует, поэтому тип члена c после смешивания будетnever
.
6.2 Объединение свойств небазового типа с одинаковым именем
В приведенном выше примере случается так, что типы внутренних членов c в интерфейсе X и интерфейсе Y являются базовыми типами данных, поэтому что произойдет, если они не будут базовыми типами данных. Давайте рассмотрим конкретный пример:
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }
interface A { x: D; }
interface B { x: E; }
interface C { x: F; }
type ABC = A & B & C;
let abc: ABC = {
x: {
d: true,
e: 'semlinker',
f: 666
}
};
console.log('abc:', abc);
После успешного выполнения приведенного выше кода консоль выведет следующие результаты:
Как видно из приведенного выше рисунка, при смешивании нескольких типов, если существует один и тот же член, а тип члена не является базовым типом данных, его можно успешно объединить.
7. Функции TypeScript
7.1 Различия между функциями TypeScript и функциями JavaScript
TypeScript | JavaScript |
---|---|
Содержит тип | нет типа |
стрелочная функция | Стрелочные функции (ES2015) |
тип функции | нет типа функции |
Обязательные и необязательные параметры | Все параметры являются необязательными |
параметры по умолчанию | параметры по умолчанию |
остальные параметры | остальные параметры |
перегрузка функций | Нет перегрузки функций |
7.2 Стрелочные функции
1. Общая грамматика
myBooks.forEach(() => console.log('reading'));
myBooks.forEach(title => console.log(title));
myBooks.forEach((title, idx, arr) =>
console.log(idx + '-' + title);
);
myBooks.forEach((title, idx, arr) => {
console.log(idx + '-' + title);
});
2. Пример использования
// 未使用箭头函数
function Book() {
let self = this;
self.publishDate = 2016;
setInterval(function () {
console.log(self.publishDate);
}, 1000);
}
// 使用箭头函数
function Book() {
this.publishDate = 2016;
setInterval(() => {
console.log(this.publishDate);
}, 1000);
}
7.3 Типы параметров и возвращаемые типы
function createUserId(name: string, id: number): string {
return name + id;
}
7.4 Типы функций
let IdGenerator: (chars: string, nums: number) => string;
function createUserId(name: string, id: number): string {
return name + id;
}
IdGenerator = createUserId;
7.5 Дополнительные параметры и параметры по умолчанию
// 可选参数
function createUserId(name: string, id: number, age?: number): string {
return name + id;
}
// 默认参数
function createUserId(
name: string = "semlinker",
id: number,
age?: number
): string {
return name + id;
}
При объявлении функции вы можете передать?
номер для определения дополнительных параметров, таких какage?: number
эта форма.При реальном использовании следует учитывать, что необязательные параметры должны располагаться после обычных параметров, иначе это вызовет ошибки компиляции..
7.6 Остальные параметры
function push(array, ...items) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
7.7 Перегрузка функций
Перегрузка функций или перегрузка методов — это возможность создавать несколько методов с одинаковыми именами и разным количеством или типами параметров.
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
// type Combinable = string | number;
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
В приведенном выше коде мы предоставляем определения нескольких типов функций для функции добавления для реализации перегрузки функций. В TypeScript, помимо перегрузки обычных функций, мы также можем перегружать методы-члены в классах.
Перегрузка метода означает, что метод имеет одно и то же имя и разные параметры в одном и том же классе (другой тип параметра, другое количество параметров или другой порядок параметров при одинаковом количестве параметров). которым метод выполняет действие. Поэтому условия перегрузки методов-членов в классе таковы: в одном классе имена методов одинаковы, а списки параметров разные. Давайте рассмотрим пример перегрузки метода-члена:
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: string, b: number): string;
add(a: number, b: string): string;
add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
}
const calculator = new Calculator();
const result = calculator.add('Semlinker', ' Kakuqo');
Предупреждение здесь заключается в том, что когда компилятор TypeScript обрабатывает перегрузку функций, он просматривает список перегрузок и пытается использовать первое определение перегрузки. Используйте это, если оно совпадает. Поэтому при определении перегрузок обязательно сначала укажите наиболее точное определение. Также в классе «Калькулятор»add(a: Combinable, b: Combinable){ }
Не является частью перегруженного списка, поэтому для метода добавления члена мы определяем только четыре перегруженных метода.
8. Массивы TypeScript
8.1 Деструктуризация массива
let x: number; let y: number; let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
8.2 Оператор расширения массива
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];
8.3 Обход массива
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
console.log(i);
}
9. TypeScript-объекты
9.1 Деструктуризация объекта
let person = {
name: "Semlinker",
gender: "Male",
};
let { name, gender } = person;
9.2 Оператор распространения объекта
let person = {
name: "Semlinker",
gender: "Male",
address: "Xiamen",
};
// 组装对象
let personWithAge = { ...person, age: 33 };
// 获取除了某些项外的其它项
let { name, ...rest } = person;
10. Интерфейс TypeScript
В объектно-ориентированных языках интерфейс — очень важная концепция, это абстракция поведения, и то, как действовать, должно быть реализовано классом.
Интерфейсы в TypeScript — очень гибкая концепция, за исключением того, что их можно использовать дляабстрактная часть поведения классаКроме того, его также часто используют для описания «формы предмета (Shape)».
10.1 Форма объектов
interface Person {
name: string;
age: number;
}
let semlinker: Person = {
name: "semlinker",
age: 33,
};
10.2 Необязательные | свойства только для чтения
interface Person {
readonly name: string;
age?: number;
}
Свойства только для чтения используются для ограничения изменений значения объекта только тогда, когда он только что создан. Кроме того, TypeScript также предоставляетReadonlyArray<T>
тип, аналогичныйArray<T>
Аналогично, за исключением того, что все изменяемые методы удаляются, что гарантирует невозможность изменения массива после создания.
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
10.3 Произвольные свойства
Иногда мы хотим разрешить интерфейсу иметь другие произвольные свойства в дополнение к обязательным и необязательным свойствам, тогда мы можем использоватьподпись индексаформу, отвечающую вышеуказанным требованиям.
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
const p1 = { name: "semlinker" };
const p2 = { name: "lolo", age: 5 };
const p3 = { name: "kakuqo", sex: 1 }
10.4 Разница между интерфейсами и псевдонимами типов
1.Objects/Functions
И интерфейсы, и псевдонимы типов могут использоваться для описания формы объекта или сигнатуры функции:
интерфейс
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
введите псевдоним
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
2.Other Types
В отличие от интерфейсных типов, псевдонимы типов могут использоваться для некоторых других типов, таких как примитивные типы, типы объединения и кортежи:
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
3.Extend
Интерфейсы и псевдоним типа могут быть расширены, но синтаксис отличается. Кроме того, интерфейсы и псевдоним типа не являются взаимоисключающими. Интерфейс может продлить псевдоним типа, и это невозможно.
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
}
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4.Implements
Класс может реализовать тип интерфейса или псевдоним таким же образом, но не может быть достигнут с использованием комбинированного типа класса типов определений псевдонимов:
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x = 1;
y = 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x = 1;
y = 2;
}
type PartialPoint = { x: number; } | { y: number; };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
x = 1;
y = 2;
}
5.Declaration merging
В отличие от псевдонимов типов, интерфейсы можно определять несколько раз, и они автоматически объединяются в один интерфейс.
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
11. Классы TypeScript
11.1 Атрибуты и методы классов
В объектно-ориентированных языках класс представляет собой конструкцию объектно-ориентированного языка программирования, схему создания объектов и описывает общие свойства и методы созданных объектов.
В TypeScript мы можем передатьClass
ключевое слово для определения класса:
class Greeter {
// 静态属性
static cname: string = "Greeter";
// 成员属性
greeting: string;
// 构造函数 - 执行初始化操作
constructor(message: string) {
this.greeting = message;
}
// 静态方法
static getClassName() {
return "Class name is Greeter";
}
// 成员方法
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
Так в чем же разница между свойствами-членами и статическими свойствами, методами-членами и статическими методами? Без особых объяснений, давайте взглянем на скомпилированный код ES5:
"use strict";
var Greeter = /** @class */ (function () {
// 构造函数 - 执行初始化操作
function Greeter(message) {
this.greeting = message;
}
// 静态方法
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// 成员方法
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// 静态属性
Greeter.cname = "Greeter";
return Greeter;
}());
var greeter = new Greeter("world");
11.2 Частные поля ECMAScript
Поддерживается начиная с TypeScript 3.8Частные поля ECMAScript, использовать следующим образом:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
с обычными свойствами (даже с использованиемprivate
свойства, объявленные модификатором), приватные поля учитывают следующие правила:
- Частные поля начинаются с
#
характер, иногда мы называем его частным именем; - Каждое имя частного поля уникально квалифицировано для содержащего его класса;
- Модификаторы доступности TypeScript (например, public или private) нельзя использовать в закрытых полях;
- К закрытым полям нельзя получить доступ за пределами содержащего класса или даже обнаружить.
11.3 Аксессуар
В TypeScript мы можем передатьgetter
а такжеsetter
метод для достижения инкапсуляции данных и проверки достоверности, чтобы предотвратить появление аномальных данных.
let passcode = "Hello TypeScript";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "Hello TypeScript") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
console.log(employee.fullName);
}
11.4 Наследование классов
Наследование — это иерархическая модель, которая связывает классы с классами. Относится к способности класса (называемого подклассом, подинтерфейсом) наследовать функции другого класса (называемого родительским классом, родительским интерфейсом) и добавлять свои собственные новые функции. Наследование — это класс и класс или интерфейс. интерфейсы.
наследование - этоis-a связь:
В TypeScript мы можем передатьextends
ключевое слово для реализации наследования:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name); // 调用父类的构造函数
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
11.5 Абстрактные классы
использоватьabstract
Класс, объявленный ключевым словом, мы называем его абстрактным классом. Невозможно создать экземпляр абстрактного класса, поскольку он содержит один или несколько абстрактных методов. Так называемый абстрактный метод относится к методу, который не содержит конкретной реализации:
abstract class Person {
constructor(public name: string){}
abstract say(words: string) :void;
}
// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error
Абстрактные классы не могут быть созданы напрямую, мы можем создавать экземпляры только подклассов, которые реализуют все абстрактные методы. В частности, следующим образом:
abstract class Person {
constructor(public name: string){}
// 抽象方法
abstract say(words: string) :void;
}
class Developer extends Person {
constructor(name: string) {
super(name);
}
say(words: string): void {
console.log(`${this.name} says ${words}`);
}
}
const lolo = new Developer("lolo");
lolo.say("I love ts!"); // lolo says I love ts!
11.6 Перегрузка метода класса
В предыдущих главах мы представили перегрузку функций. Он также поддерживает перегрузку методов класса. Например, в следующем примере мы перегрузилиProductService
КатегорияgetProducts
Метод участника:
class ProductService {
getProducts(): void;
getProducts(id: number): void;
getProducts(id?: number) {
if(typeof id === 'number') {
console.log(`获取id为 ${id} 的产品信息`);
} else {
console.log(`获取所有的产品信息`);
}
}
}
const productService = new ProductService();
productService.getProducts(666); // 获取id为 666 的产品信息
productService.getProducts(); // 获取所有的产品信息
12. Обобщения TypeScript
В разработке программного обеспечения мы не только создаем согласованные четко определенные API, но и учитываем возможность повторного использования. Компоненты могут поддерживать не только текущие типы данных, но и будущие типы данных, что дает вам большую гибкость при создании больших систем.
В таких языках, как C# и Java, дженерики можно использовать для создания повторно используемых компонентов, которые могут поддерживать несколько типов данных. Это позволяет пользователям использовать компоненты с собственными типами данных.
Основная цель разработки универсальных шаблонов — предоставить значимые ограничения между членами, которыми могут быть: члены экземпляра класса, методы класса, параметры функции и возвращаемые значения функции.
Generics — это шаблон, который позволяет одной и той же функции принимать параметры разных типов. Лучше использовать дженерики для создания повторно используемых компонентов, чем использовать любой тип, потому что дженерики сохраняют типы параметров.
12.1 Общий синтаксис
Для читателей, плохо знакомых с дженериками TypeScript, первое<T>
Синтаксис покажется вам незнакомым. На самом деле ничего особенного, как и при передаче параметров, мы передаем тип, который хотим использовать для вызова конкретной функции.
Ссылаясь на картинку выше, когда мы вызываем identity<Number>(1)
,Number
Типы похожи на параметры1
то же самое появитсяT
заполняет тип в любом месте. на фото<T>
ВнутреннийT
называется переменной типа, это заполнитель для типа, который мы хотим передать функции идентификации, и он назначаетсяvalue
Параметр используется вместо его типа: в настоящее времяT
Действует как тип, а не как конкретный числовой тип.
вT
представлятьType, обычно используемое в качестве имени переменной первого типа при определении универсального. Но по фактуT
Можно заменить любым допустимым именем. КромеT
Кроме того, ниже приведены значения общих общих переменных:
- K (ключ): представляет тип ключа в объекте;
- V (значение): представляет тип значения в объекте;
- E (элемент): указывает тип элемента.
На самом деле, вместо того, чтобы определять только одну переменную типа, мы можем ввести любое количество переменных типа, которые мы хотим определить. Например, мы вводим переменную нового типаU
, который используется для расширения нашего определенногоidentity
функция:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
Вместо явно установки значений для переменных типа, более распространенная практика заключается в том, чтобы компилятор автоматически выбрал эти типы, что делает очиститель кода. Мы можем исключить угловые скобки полностью, например:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
Для приведенного выше кода компилятор достаточно умен, чтобы знать наши типы параметров и назначать их T и U без явного указания их разработчиком.
12.2 Общие интерфейсы
interface GenericIdentityFn<T> {
(arg: T): T;
}
12.3 Общие классы
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
12.4 Общие типы инструментов
Для удобства разработчиков в TypeScript встроены некоторые часто используемые типы инструментов, такие как Partial, Required, Readonly, Record и ReturnType. Для экономии места здесь мы лишь кратко представляем тип инструмента Partial. Однако перед конкретным введением мы должны сначала представить некоторые соответствующие базовые знания, чтобы читатели могли самостоятельно изучить другие типы инструментов.
1.typeof
В машинописном языке,typeof
Операторы могут использоваться для получения типа объявления переменной или объекта.
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem= typeof sem; // -> Person
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
2.keyof
keyof
Представленный в TypeScript 2.1 оператор может использоваться для получения всех ключей типа, а его возвращаемый тип — тип объединения.
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
В TypeScript поддерживаются два типа сигнатур индекса: числовое индексирование и индексирование строк:
interface StringArray {
// 字符串索引 -> keyof StringArray => string | number
[index: string]: string;
}
interface StringArray1 {
// 数字索引 -> keyof StringArray1 => number
[index: number]: string;
}
Для поддержки обоих типов индексов требуется, чтобы возвращаемое значение числового индекса было подклассом возвращаемого значения строкового индекса.Причина в том, что при использовании числового индексирования JavaScript сначала преобразует числовой индекс в строковый индекс при выполнении операции индексирования.. такkeyof { [x: string]: Person }
Результат вернетсяstring | number
.
3.in
in
Используется для перебора типов перечисления:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
4.infer
В операторе условного типа вы можете использоватьinfer
Объявите переменную типа и используйте ее.
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
в приведенном выше кодеinfer R
Это объявление переменной для переноса типа возвращаемого значения сигнатуры входящей функции.Проще говоря, она используется для получения типа возвращаемого значения функции для удобства.
5.extends
Иногда определяемые нами дженерики не хотят быть слишком гибкими или хотят наследовать определенные классы и т. д., мы можем добавить универсальные ограничения с помощью ключевого слова extends.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
Теперь эта универсальная функция ограничена, поэтому она больше не применима к произвольным типам:
loggingIdentity(3); // Error, number doesn't have a .length property
На данный момент нам нужно передать значение, соответствующее типу ограничения, которое должно содержать необходимые атрибуты:
loggingIdentity({length: 10, value: 3});
6.Partial
Partial<T>
Функция состоит в том, чтобы сделать все свойства в типе необязательными.?
.
определение:
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
В приведенном выше коде первый проходkeyof T
получатьT
всех имен свойств, затем используйтеin
Перебрать и присвоить значенияP
, наконец черезT[P]
Получите соответствующее значение атрибута. Середина?
, чтобы сделать все свойства необязательными.
Пример:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "Learn TS",
description: "Learn TypeScript",
};
const todo2 = updateTodo(todo1, {
description: "Learn TypeScript Enum",
});
надupdateTodo
метод, мы используемPartial<T>
Тип инструмента, определениеfieldsToUpdate
имеет типPartial<Todo>
,который:
{
title?: string | undefined;
description?: string | undefined;
}
Тринадцать, декораторы TypeScript
13.1 Что такое декораторы
- это выражение
- Возвращает функцию после выполнения выражения
- Входными параметрами функций являются Target, Name и Descriptor соответственно.
- После выполнения этой функции он может вернуть объект дескриптора, который используется для настройки целевого объекта
13.2 Классификация декораторов
- Декораторы класса
- Декораторы недвижимости
- Декораторы методов
- Декораторы параметров
Следует отметить, что включить функцию экспериментального декоратора, вы должныtsconfig.json
Давать возможностьexperimentalDecorators
Опции компилятора:
Командная строка:
tsc --target ES5 --experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
13.3 Декораторы классов
Объявление декоратора класса:
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
Декораторы классов, как следует из названия, используются для оформления классов. Он принимает один параметр:
- target: TFunction — украшенный класс
После первого просмотра осталось не очень приятное впечатление. Ничего страшного, сразу возьмем пример:
function Greeter(target: Function): void {
target.prototype.greet = function (): void {
console.log("Hello Semlinker!");
};
}
@Greeter
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello Semlinker!';
В приведенном выше примере мы определилиGreeter
декоратор класса, и мы использовали@Greeter
Синтаксический сахар для использования декораторов.
Дружеское напоминание: читатели могут напрямую скопировать приведенный выше код вTypeScript Playgroundбеги смотреть результаты.
Некоторые читатели могут спросить, пример всегда выводитHello Semlinker!
, вы можете настроить выходное приветствие? Это хороший вопрос, и ответ положительный.
Конкретная реализация выглядит следующим образом:
function Greeter(greeting: string) {
return function (target: Function) {
target.prototype.greet = function (): void {
console.log(greeting);
};
};
}
@Greeter("Hello TS!")
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello TS!';
13.4 Декораторы свойств
Объявление декоратора свойств:
declare type PropertyDecorator = (target:Object,
propertyKey: string | symbol ) => void;
Декораторы свойств, как следует из названия, используются для оформления свойств класса. Он принимает два параметра:
- target: Object - класс, который нужно украсить
- propertyKey:string|symbol — имя свойства декорируемого класса
Куй железо, пока горячо, разогреем на примере:
function logProperty(target: any, key: string) {
delete target[key];
const backingField = "_" + key;
Object.defineProperty(target, backingField, {
writable: true,
enumerable: true,
configurable: true
});
// property getter
const getter = function (this: any) {
const currVal = this[backingField];
console.log(`Get: ${key} => ${currVal}`);
return currVal;
};
// property setter
const setter = function (this: any, newVal: any) {
console.log(`Set: ${key} => ${newVal}`);
this[backingField] = newVal;
};
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@logProperty
public name: string;
constructor(name : string) {
this.name = name;
}
}
const p1 = new Person("semlinker");
p1.name = "kakuqo";
В приведенном выше коде мы определяемlogProperty
функция для отслеживания действий пользователя с ресурсом. При успешном выполнении кода в консоль будут выведены следующие результаты:
Set: name => semlinker
Set: name => kakuqo
13.5 Декораторы методов
Объявление декоратора метода:
declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
Декораторы методов, как следует из названия, используются для оформления методов класса. Он принимает три параметра:
- target: Object - класс, который нужно украсить
- propertyKey: string | symbol — имя метода
- descriptor: TypePropertyDescript — дескриптор свойства
Без лишних слов, давайте сразу к примеру:
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
let originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("wrapped function: before invoking " + propertyKey);
let result = originalMethod.apply(this, args);
console.log("wrapped function: after invoking " + propertyKey);
return result;
};
}
class Task {
@log
runTask(arg: any): any {
console.log("runTask invoked, args: " + arg);
return "finished";
}
}
let task = new Task();
let result = task.runTask("learn ts");
console.log("result: " + result);
После успешного выполнения приведенного выше кода консоль выведет следующие результаты:
"wrapped function: before invoking runTask"
"runTask invoked, args: learn ts"
"wrapped function: after invoking runTask"
"result: finished"
Давайте взглянем на декораторы параметров.
13.6 Декораторы параметров
Объявление декоратора параметров:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
parameterIndex: number ) => void
Декоратор параметров, как следует из названия, используется для оформления параметров функции и получает три параметра:
- target: Object - класс, который нужно украсить
- propertyKey: string | symbol — имя метода
- параметрИндекс: число - значение индекса параметра в методе
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase;
}
}
После успешного выполнения приведенного выше кода консоль выведет следующие результаты:
"The parameter in position 0 at Greeter has been decorated"
14. Новые возможности TypeScript 4.0
В TypeScript 4.0 появилось много новых функций, здесь мы кратко расскажем о двух из них.
14.1 Вывод атрибутов класса для конструкторов
когдаnoImplicitAny
С включенными свойствами конфигурации TypeScript 4.0 может использовать анализ потока управления для подтверждения типа свойств в классе:
class Person {
fullName; // (property) Person.fullName: string
firstName; // (property) Person.firstName: string
lastName; // (property) Person.lastName: string
constructor(fullName: string) {
this.fullName = fullName;
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
Однако для приведенного выше кода, если используется версия до TypeScript 4.0, например версия 3.9.2, компилятор выдаст следующее сообщение об ошибке:
class Person {
// Member 'fullName' implicitly has an 'any' type.(7008)
fullName; // Error
firstName; // Error
lastName; // Error
constructor(fullName: string) {
this.fullName = fullName;
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
Вывод типов свойств класса из конструктора удобен для нас. Однако во время использования, если мы не можем гарантировать, что все свойства члена назначены, то свойство может рассматриваться какundefined
.
class Person {
fullName; // (property) Person.fullName: string
firstName; // (property) Person.firstName: string | undefined
lastName; // (property) Person.lastName: string | undefined
constructor(fullName: string) {
this.fullName = fullName;
if(Math.random()){
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
}
14.2 Помеченные элементы кортежа
В следующем примере мы используем тип кортежа для объявления типов остальных параметров:
function addPerson(...args: [string, number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`)
}
addPerson("lolo", 5); // Person info: name: lolo, age: 5
В самом деле, для вышеперечисленногоaddPerson
функцию, мы также можем реализовать ее следующим образом:
function addPerson(name: string, age: number) {
console.log(`Person info: name: ${name}, age: ${age}`)
}
Эти два способа не сильно отличаются, но для первого способа мы не можем установить имя первого параметра и второго параметра. Хотя это не влияет на проверку типов, отсутствие меток в позициях кортежа затрудняет их использование. Чтобы улучшить работу разработчиков с кортежами, TypeScript 4.0 поддерживает настройку тегов для типов кортежей:
function addPerson(...args: [name: string, age: number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`);
}
После этого, когда мы используемaddPerson
метод Intellisense TypeScript становится более дружественным.
// 未使用标签的智能提示
// addPerson(args_0: string, args_1: number): void
function addPerson(...args: [string, number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`)
}
// 已使用标签的智能提示
// addPerson(name: string, age: number): void
function addPerson(...args: [name: string, age: number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`);
}
15. Контекст компиляции
15.1 Роль tsconfig.json
- Корневой путь, используемый для идентификации проекта TypeScript;
- Используется для настройки компилятора TypeScript;
- Используется для указания скомпилированного файла.
15.2 Важные поля tsconfig.json
- files — задает имя компилируемых файлов;
- include — задайте файлы, которые необходимо скомпилировать, поддержите сопоставление с образцом пути;
- исключить - установить файлы, которые не нужно компилировать, поддерживать сопоставление с образцом пути;
- compileOptions — установка параметров, связанных с процессом компиляции.
15.3 Параметры компилятораOptions
КомпиляторOptions поддерживает множество опций, наиболее распространенными являютсяbaseUrl
,target
,baseUrl
,moduleResolution
а такжеlib
Ждать.
Подробное описание каждой опции вcompileOptions приведено ниже:
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}
16. Вспомогательные средства разработки TypeScript
16.1 TypeScript Playground
Введение: онлайн-среда выполнения TypeScript, официально предоставляемая TypeScript, с помощью которой вы можете легко изучить знания, связанные с TypeScript, и функции различных версий.
Интернет-адрес:www.typescriptlang.org/play/
В дополнение к официальной игровой площадке TypeScript вы также можете выбрать другие игровые площадки, такие какcodepen.io,stackblitzилиjsbin.comЖдать.
16.2 TypeScript UML Playground
Введение. Онлайн-инструмент TypeScript UML, который позволяет создавать диаграммы классов UML для указанного кода TypeScript.
Интернет-адрес:tsuml-demo.firebaseapp.com/
16.3 JSON TO TS
Введение. Онлайн-инструмент TypeScript, с помощью которого можно создать соответствующее определение интерфейса TypeScript для указанных данных JSON.
Интернет-адрес:www.jsontots.com/
Помимо использованияjsontotsВ дополнение к онлайн-инструментам, вы также можете установить его для тех, кто использует VSCode IDE.JSON to TSрасширение для быстрого завершенияJSON to TSконверсионная работа.
16.4 Schemats
Резюме: С помощью Schemats вы можете автоматически генерировать определения интерфейса TypeScript на основе схем в базах данных SQL (Postgres, MySQL).
Интернет-адрес:GitHub.com/sweetIQ/Сгенерировать…
16.5 TypeScript AST Viewer
Введение: Онлайн-инструмент TypeScript AST, с помощью которого вы можете просматривать абстрактное синтаксическое дерево AST (Abstract Syntax Tree), соответствующее указанному коду TypeScript.
Интернет-адрес:ts-ast-viewer.com/
Для тех, кто знает АСТ, даastexplorerЭтот онлайн-инструмент должен быть вам знаком. Помимо поддержки JavaScript, инструмент также поддерживает синтаксический анализ таких форматов, как CSS, JSON, RegExp, GraphQL и Markdown.
16.6 TypeDoc
Описание: TypeDoc используется для преобразования комментариев в исходном коде TypeScript в документы HTML или модели JSON. Он гибко масштабируется и поддерживает несколько конфигураций.
Интернет-адрес:typedoc.org/
16.7 TypeScript ESLint
Введение: использованиеTypeScript ESLintЭто может помочь нам стандартизировать качество кода и повысить эффективность командной разработки.
Интернет-адрес:typescript-eslint.io/
правильноTypeScript ESLintДля тех, кто заинтересован в проекте и хочет применить его в проекте, вы можете обратиться к«Как элегантно использовать ESLint и Prettier в проектах Typescript»Эта статья.
Друзья, которые могут настоять на просмотре здесь, все «настоящая любовь».awesome-typescript.