В этой статье брат Абао поделится 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
Эти два метода используются для установки значения и получения значения.