Редкое учебное пособие по TS (1,8 Вт слов)

внешний интерфейс TypeScript
Редкое учебное пособие по TS (1,8 Вт слов)

Первое использование Апогея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 будет сужен доbooleantype, так что его нельзя будет присвоить типу 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.

17. Справочные ресурсы