Подсчет этих странных символов в TS

внешний интерфейс TypeScript
Подсчет этих странных символов в TS

В этой статье брат Абао поделится 10 «странными» символами, с которыми он столкнулся в процессе изучения TypeScript на протяжении многих лет. В ней есть какие-то символы. Брат Абао тоже "запутался", когда впервые увидел ее. Надеюсь, эта статья сможет чем-то помочь тем, кто изучает TypeScript.

Хорошо, начнем с первого символа -!ненулевой оператор утверждения.

1. Оператор ненулевого утверждения !

Новый оператор постфиксного выражения в контексте, когда средство проверки типов не может определить тип!Может использоваться для подтверждения того, что объект операции не имеет нулевого и неопределенного типа.В частности, x!исключает null и undefined из диапазона x.

Итак, что именно делает оператор ненулевого утверждения? Давайте сначала рассмотрим некоторые сценарии использования оператора ненулевого утверждения.

1.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
}

1.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.

1.3 Утверждение детерминированного присваивания

Детерминированные утверждения присваивания были введены в TypeScript 2.7, что позволяет размещать!No., сообщает 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 знает, что свойству будет определенно присвоено значение.

2. ?.оператор

В TypeScript 3.7 реализована одна из наиболее востребованных функций ECMAScript: опциональная цепочка. С дополнительной цепочкой, когда мы пишем код, если мы сталкиваемсяnullилиundefinedВы можете немедленно остановить выполнение определенных выражений. Ядро дополнительного цепочка нового?.Оператор, он поддерживает следующий синтаксис:

obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

Здесь мы берем пример доступа к необязательным свойствам:

const val = a?.b;

Чтобы лучше понять необязательную цепочку, давайте взглянем наconst val = a?.bОператор компилирует сгенерированный код ES5:

var val = a === null || a === void 0 ? void 0 : a.b;

Приведенный выше код автоматически проверит, является ли объект anullилиundefined, если так вернитесь немедленноundefined, что немедленно останавливает выполнение определенных выражений. Возможно, вы думали, что можете использовать?.заменить многие виды использования&&Код для выполнения нулевой проверки:

if(a && a.b) { } 

if(a?.b){ }
/**
* if(a?.b){ } 编译后的ES5代码
* 
* if(
*  a === null || a === void 0 
*  ? void 0 : a.b) {
* }
*/

Но следует отметить, что,?.а также&&Оператор ведет себя немного иначе,&&посвященный обнаружениюfalsyТакие значения, как пустая строка, 0, NaN, null, false и т. д. а также?.будет только проверять, что объектnullилиundefined, нет "короткого замыкания" для 0 или пустой строки.

2.1 Доступ к дополнительным элементам

Помимо поддержки доступа к необязательным атрибутам, необязательная цепочка также поддерживает доступ к необязательным элементам, который ведет себя аналогично доступу к необязательным атрибутам, за исключением того, что доступ к необязательным элементам позволяет нам получить доступ к неидентифицирующим атрибутам, таким как любые строки, числовые индексы и символы:

function tryGetArrayElement<T>(arr?: T[], index: number = 0) {
  return arr?.[index];
}

После компиляции приведенного выше кода генерируется следующий код ES5:

"use strict";
function tryGetArrayElement(arr, index) {
    if (index === void 0) { index = 0; }
    return arr === null || arr === void 0 ? void 0 : arr[index];
}

Наблюдая за сгенерированным кодом ES5, становится ясно, чтоtryGetArrayElementМетод автоматически определит, является ли значение входного параметра arr равнымnullилиundefined, тем самым обеспечивая надежность нашего кода.

2.2 Дополнительные цепочки и вызовы функций

Необязательная цепочка также может использоваться при попытке вызвать метод, который может не существовать. Это полезно во время реальной разработки. Метод в системе недоступен, что может быть вызвано несоответствием версии или проблемами совместимости пользовательских устройств. Когда функция вызывается, если вызываемый метод не существует, использование необязательной цепочки может привести к автоматическому возврату выражения.undefinedвместо того, чтобы генерировать исключение.

Дополнительные вызовы также просты в использовании, например:

let result = obj.customMethod?.();

Сгенерированный код компиляции кода TypeScript ES5 выглядит следующим образом:

var result = (_a = obj.customMethod) === null
  || _a === void 0 ? void 0 : _a.call(obj);

Кроме того, при использовании необязательных вызовов мы должны обратить внимание на следующие два соображения:

  • Если есть имя свойства и значение, соответствующее имени свойства, не является типом функции, используйте?.по-прежнему производитTypeErrorаномальный.
  • Поведение необязательной цепочки ограничено доступом к атрибуту, вызовом и доступом к элементу — оно не переносится на последующие выражения, т. е. необязательный вызов не предотвращаетa?.b / someMethod()операция деления в выражении илиsomeMethodвызов метода.

3. Оператор объединения нулевых значений

В TypeScript 3.7, в дополнение к необязательной цепочке, представленной ранее?.Кроме того, вводится новый логический оператор — оператор объединения null??.Когда левый операнд равен нулю или не определен, возвращается правый операнд, в противном случае возвращается левый операнд..

И логическое ИЛИ||В отличие от операторов, логическое ИЛИ возвращает правый операнд, когда левый операнд является ложным значением. Тем не менее, если вы используете || для установки значений по умолчанию для некоторых переменных, вы можете столкнуться с неожиданным поведением. Например, если это ложное значение ('', NaN или 0).

Вот конкретный пример:

const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"

const baz = 0 ?? 42;
console.log(baz); // 输出:0

После компиляции приведенного выше кода TS будет сгенерирован следующий код ES5:

"use strict";
var _a, _b;
var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string';
console.log(foo); // 输出:"default string"

var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42;
console.log(baz); // 输出:0

Наблюдая за приведенным выше кодом, мы получили более интуитивное представление о том, как оператор объединения нулевых значений решает предыдущую проблему.||Возможные проблемы с операторами. Давайте представим характеристики оператора объединения нулевых значений и некоторые меры предосторожности при его использовании.

3.1 короткое замыкание

Когда левое выражение нулевого коалисового оператора неnullилиundefined, правое выражение не оценивается.

function A() { console.log('A was called'); return undefined;}
function B() { console.log('B was called'); return false;}
function C() { console.log('C was called'); return "foo";}

console.log(A() ?? C());
console.log(B() ?? C());

После запуска приведенного выше кода консоль выведет следующие результаты:

A was called 
C was called 
foo 
B was called 
false 

3.2 Нельзя использовать с операторами && или ||

если нулевой оператор объединения??Использование ?? непосредственно в сочетании с операторами И (&&) и ИЛИ (||) не будет работать. В этом случае выдается SyntaxError.

// '||' and '??' operations cannot be mixed without parentheses.(5076)
null || undefined ?? "foo"; // raises a SyntaxError

// '&&' and '??' operations cannot be mixed without parentheses.(5076)
true && undefined ?? "foo"; // raises a SyntaxError

Но при использовании круглых скобок явно указать, что приоритет возможен, например:

(null || undefined ) ?? "foo"; // 返回 "foo"

3.3 Отношение к необязательному оператору цепочки ?.

Нулевой оператор объединения для неопределенного и нулевого, необязательный оператор цепочки?.тоже так. Необязательный оператор цепочки, полезный для доступа к объектам, свойства которых могут быть неопределенными или нулевыми.

interface Customer {
  name: string;
  city?: string;
}

let customer: Customer = {
  name: "Semlinker"
};

let customerCity = customer?.city ?? "Unknown city";
console.log(customerCity); // 输出:Unknown city

Ранее мы представили сценарии применения оператора объединения нулевых значений и некоторые меры предосторожности при его использовании.Этот оператор можно использовать не только в TypeScript 3.7 и выше. Конечно, вы также можете использовать его в среде JavaScript, но вам нужно использовать Babel, который также поддерживает оператор объединения null в Babel 7.8.0.

В-четвертых, ?: необязательный атрибут

В объектно-ориентированных языках интерфейс является очень важной концепцией, которая представляет собой абстракцию поведения и то, как именно нужно действовать.Интерфейс в TypeScript — очень гибкое понятие, помимо абстрагирования части поведения класса, его также часто используют для описания «формы объекта»..

Использование в TypeScriptinterfaceключевое слово для объявления интерфейса:

interface Person {
  name: string;
  age: number;
}

let semlinker: Person = {
  name: "semlinker",
  age: 33,
};

В приведенном выше коде мы объявляемPersonинтерфейс, который содержит два обязательных свойстваnameа такжеage. При инициализации переменной типа Person, если свойство отсутствует, компилятор TypeScript выдаст соответствующее сообщение об ошибке, например:

// Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.(2741)
let lolo: Person  = { // Error
  name: "lolo"  
}

Чтобы решить вышеуказанную проблему, мы можем объявить свойство необязательным:

interface Person {
  name: string;
  age?: number;
}

let lolo: Person  = {
  name: "lolo"  
}

4.1 Тип инструмента

4.1.1 Partial<T>

В реальном процессе разработки проекта, чтобы повысить скорость повторного использования кода, мы можем использовать встроенные типы инструментов TypeScript.Partial<T>Чтобы быстро сделать свойство, определенное в типе интерфейса, необязательным:

interface PullDownRefreshConfig {
  threshold: number;
  stop: number;
}

/**
 * type PullDownRefreshOptions = {
 *   threshold?: number | undefined;
 *   stop?: number | undefined;
 * }
 */ 
type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

ты думаешьPartial<T>Очень удобно, давайте посмотрим, как это достигнуто:

/**
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};
4.1.2 Required<T>

Поскольку мы можем быстро объявить все свойства, определенные в интерфейсе, необязательными, можем ли мы сделать все необязательные свойства обязательными? Ответ положительный, для этого требования мы можем использоватьRequired<T>Тип инструмента, конкретное использование заключается в следующем:

interface PullDownRefreshConfig {
  threshold: number;
  stop: number;
}

type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

/**
 * type PullDownRefresh = {
 *   threshold: number;
 *   stop: number;
 * }
 */
type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

Опять же, давайте посмотрим наRequired<T>Как реализован тип инструмента:

/**
 * Make all properties in T required
 */
type Required<T> = {
  [P in keyof T]-?: T[P];
};

это былоRequired<T>Внутри типа инструмента через-?удалены необязательные атрибуты?, изменяя атрибут с необязательного на обязательный.

5. & оператор

Кросс-типирование в TypeScript — это объединение нескольких типов в один тип. пройти через&Операторы могут объединять несколько существующих типов в один тип, который содержит все необходимые свойства типа.

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

let point: Point = {
  x: 1,
  y: 1
}

В приведенном выше коде мы сначала определяемPartialPointXвведите, затем используйте&оператор создает новыйPointтип, который представляет точку с координатами x и y, а затем определяетPointпеременная типа и инициализирована.

5.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.

5.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);

После успешного выполнения приведенного выше кода консоль выведет следующие результаты:

Как видно из приведенного выше рисунка, при смешивании нескольких типов, если существует один и тот же член, а тип члена не является базовым типом данных, его можно успешно объединить.

Шесть, |разделитель

В TypeScript Union Types означает, что значение может быть одним из нескольких типов.|Отделить каждый тип. Совместные типы обычноnullилиundefinedиспользовать вместе:

const sayHello = (name: string | undefined) => { /* ... */ };

В приведенном выше примереnameТипstring | undefinedозначает, чтоstringилиundefinedСтоимость переданаsayHelloфункция.

sayHello("semlinker");
sayHello(undefined);

Кроме того, для типов объединения вы можете столкнуться со следующими вариантами использования:

let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';

в примере1,2или'click'Называемый литеральным типом, он используется для ограничения значения только одним из нескольких значений.

6.1 Защита типа

При использовании типов объединения мы должны попытаться сузить тип текущего значения до фактического типа текущего значения, а защита типа — это средство достижения сужения типа.

Защита типа — это выражение, которое выполняет проверки во время выполнения, чтобы гарантировать, что тип находится в пределах определенного диапазона. Другими словами, защита типов гарантирует, что строка является строкой, хотя ее значение также может быть числом. Защита типа не полностью отличается от обнаружения функций, основная идея состоит в том, чтобы попытаться обнаружить свойство, метод или прототип, чтобы определить, как обрабатывать значение.

В настоящее время существует четыре основных способа реализации защиты типа:

6.1.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);
  }
}
6.1.2 Ключевое слово typeof
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". Но TypeScript не мешает вам сравнивать с другими строками, язык не распознает эти выражения как охранники типа.

6.1.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'
}
6.1.4 Предикат типа для пользовательской защиты типа
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

Семь, _ разделитель чисел

TypeScript 2.7 обеспечивает поддержку числовых разделителей, как указано в предложении ECMAScript по числовым разделителям. Для числового литерала теперь вы можете группировать числа, помещая между ними знак подчеркивания в качестве разделителя:

const inhabitantsOfMunich = 1_464_301;
const distanceEarthSunInKm = 149_600_000;
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;

Разделители не изменяют значение числового литерала, но логическая группировка упрощает чтение чисел с первого взгляда. После компиляции приведенного выше кода TS будет сгенерирован следующий код ES5:

"use strict";
var inhabitantsOfMunich = 1464301;
var distanceEarthSunInKm = 149600000;
var fileSystemPermission = 504;
var bytes = 262926349;

7.1 Ограничения на использование

Хотя разделитель чисел кажется простым, существуют некоторые ограничения при его использовании. Например, вы можете добавить только между двумя числами_Разделитель. Следующие методы использования являются незаконными:

// Numeric separators are not allowed here.(6188)
3_.141592 // Error
3._141592 // Error

// Numeric separators are not allowed here.(6188)
1_e10 // Error
1e_10 // Error

// Cannot find name '_126301'.(2304)
_126301  // Error
// Numeric separators are not allowed here.(6188)
126301_ // Error

// Cannot find name 'b111111000'.(2304)
// An identifier or keyword cannot immediately follow a numeric literal.(1351)
0_b111111000 // Error

// Numeric separators are not allowed here.(6188)
0b_111111000 // Error

Конечно, вы не можете использовать более одного подряд_разделитель, например:

// Multiple consecutive numeric separators are not permitted.(6189)
123__456 // Error

7.2 Разбор разделителей

Также обратите внимание, что следующие функции для анализа чисел не поддерживают разделители:

  • Number()
  • parseInt()
  • parseFloat()

Здесь мы рассмотрим практический пример:

Number('123_456')
NaN
parseInt('123_456')
123
parseFloat('123_456')
123

Очевидно, что приведенный выше результат не соответствует нашим ожиданиям, поэтому обратите особое внимание на разделители. Конечно, для решения вышеуказанных проблем также очень просто удалить нечисловые символы. Здесь мы определяемremoveNonDigitsФункция:

const RE_NON_DIGIT = /[^0-9]/gu;

function removeNonDigits(str) {
  str = str.replace(RE_NON_DIGIT, '');
  return Number(str);
}

Функция вызывает строкуreplaceметод удаления нечисловых символов, конкретное использование выглядит следующим образом:

removeNonDigits('123_456')
123456
removeNonDigits('149,600,000')
149600000
removeNonDigits('1,407,836')
1407836

Восемь,<Type>грамматика

8.1 Утверждения TypeScript

Иногда вы столкнетесь с ситуацией, когда вы знаете больше деталей о значении, чем TypeScript. Обычно это происходит, когда вы четко знаете, что сущность имеет более точный тип, чем ее существующий тип.

Утверждения типов — это способ сказать компилятору: «Поверь мне, я знаю, что делаю». Утверждения типов аналогичны преобразованиям типов в других языках, но без специальной проверки данных и деструктурирования. Это не влияет на время выполнения, оно просто работает во время компиляции.

Утверждения типа происходят две формы:

8.1.1 Синтаксис «угловых скобок»
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
8.1.2 как синтаксис
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

8.2 Обобщения TypeScript

Для читателей, плохо знакомых с дженериками 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 без явного указания их разработчиком.

Девять, @XXX декоратор

9.1 Синтаксис декоратора

Для некоторых друзей, которые плохо знакомы с TypeScript, видят его впервые@Plugin({...})Такой синтаксис может стать неожиданностью. По сути, это синтаксис декоратора.Суть декоратора — это функция.Через декоратор мы можем легко определить метаданные, относящиеся к объекту.

@Plugin({
  pluginName: 'Device',
  plugin: 'cordova-plugin-device',
  pluginRef: 'device',
  repo: 'https://github.com/apache/cordova-plugin-device',
  platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
})
@Injectable()
export class Device extends IonicNativePlugin {}

В приведенном выше коде мы используем декоратор для сохранения соответствующей метаинформации ионно-родного плагина и@Plugin({...})середина@Символы — это просто синтаксический сахар, почему именно синтаксический сахар? Здесь мы смотрим на скомпилированный код ES5:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

var Device = /** @class */ (function (_super) {
    __extends(Device, _super);
    function Device() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Device = __decorate([
        Plugin({
            pluginName: 'Device',
            plugin: 'cordova-plugin-device',
            pluginRef: 'device',
            repo: 'https://github.com/apache/cordova-plugin-device',
            platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
        }),
        Injectable()
    ], Device);
    return Device;
}(IonicNativePlugin));

Из сгенерированного кода видно, что@Plugin({...})а также@Injectable()В конечном итоге будут преобразованы в обычный вызов метода, их вызовы в конечном итоге приведут к форме массива в качестве аргумента для__decorateфункция, в то время как__decorateВнутри функции будетDeviceКлассы вызывают соответствующие декораторы типов в качестве аргументов, тем самым расширяя соответствующие функциональные возможности.

9.2 Классификация декораторов

В TypeScript декораторы делятся на четыре категории: декораторы классов, декораторы свойств, декораторы методов и декораторы параметров.

9.2.1 декоратор класса

Декларация классического декоратора:

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беги смотреть результаты.

9.2.2 Декораторы свойств

Объявление декоратора свойства:

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
9.2.3 Декораторы методов

Объявление декоратора метода:

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" 
9.2.4 Декораторы параметров

Объявление декоратора параметров:

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" 

10. #XXX приватных полей

Поддерживается начиная с 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) нельзя использовать в закрытых полях;
  • К закрытым полям нельзя получить доступ за пределами содержащего класса или даже обнаружить.

10.1 Разница между приватными полями и приватными

Говоря об использовании#Определенные частные поля сprivateВ чем разница между полями определения модификатора? Теперь давайте посмотрим на одинprivateПример:

class Person {
  constructor(private name: string){}
}

let person = new Person("Semlinker");
console.log(person.name);

В приведенном выше коде мы создали класс Person, который используетprivateмодификатор определяет частное свойствоname, затем используйте этот класс для созданияpersonобъект, а затем передатьperson.nameпосетитьpersonЗакрытое свойство объекта, то компилятор TypeScript предложит следующее исключение:

Property 'name' is private and only accessible within class 'Person'.(2341)

Итак, как решить это исключение? Конечно, вы можете использовать утверждение типа для преобразования человека в любой тип:

console.log((person as any).name);

Таким образом, хотя запрос исключения компилятора TypeScript решен, мы все еще можем получить к нему доступ во время выполнения.PersonЧастные свойства внутри класса, почему это происходит? Давайте посмотрим на скомпилированный код ES5, возможно, вы знаете ответ:

var Person = /** @class */ (function () {
    function Person(name) {
      this.name = name;
    }
    return Person;
}());

var person = new Person("Semlinker");
console.log(person.name);

В настоящее время я считаю, что некоторым небольшим партнерам будет любопытно, и они прошли TypeScript 3.8 и выше.#Какой код будет сгенерирован после того, как будет скомпилировано приватное поле, определяемое числом:

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

Приведенный выше целевой код установлен на ES2015, который скомпилирует и сгенерирует следующий код:

"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) 
  || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
      throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};

var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) 
  || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
      throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};

var _name;
class Person {
    constructor(name) {
      _name.set(this, void 0);
      __classPrivateFieldSet(this, _name, name);
    }
    greet() {
      console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);
    }
}
_name = new WeakMap();

Соблюдая приведенный выше код, используйте#Частные поля ECMAScript, определенныеWeakMapобъект для хранения, и компилятор сгенерирует__classPrivateFieldSetа также__classPrivateFieldGetЭти два метода используются для установки значения и получения значения.

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