本文將分享這些年在學習 TypeScript 過程中,遇到的 10 大 “奇怪” 的符號。其中有一些符號,碼雲筆記第一次見的時候也覺得 “一臉懵逼”,希望本文對學習 TypeScript 的小伙伴能有一些幫助。
!
非空斷言操作符
在上下文中當類型檢查器無法斷定類型時,一個新的后綴表達式操作符 ! 可以用於斷言操作對象是非 null 和非 undefined 類型。具體而言,x! 將從 x 值域中排除 null 和 undefined 。
function myFunc(maybeString: string | undefined | null) {
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
調用函數時忽略 undefined 類型 場景
type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}
?.
運算符
TypeScript 3.7 實現了呼聲最高的 ECMAScript 功能之一:可選鏈(Optional Chaining)。有了可選鏈后,我們編寫代碼時如果遇到 null 或 undefined 就可以立即停止某些表達式的運行。可選鏈的核心是新的 ?. 運算符
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
const val = a?.b;
語句編譯生成的 ES5 代碼:
var val = a === null || a === void 0 ? void 0 : a.b;
可選鏈與函數調用
let result = obj.customMethod?.();
??
空值合並運算符
在 TypeScript 3.7 版本中除了引入了前面介紹的可選鏈 ?.
之外,也引入了一個新的邏輯運算符 —— 空值合並運算符 ??
當左側操作數為 null 或 undefined 時,其返回右側的操作數,否則返回左側的操作數。
const foo = null ?? 'default string';
console.log(foo); // 輸出:"default string"
短路
不能與 && 或 || 操作符共用
(null || undefined ) ?? "foo"; // 返回 "foo"
與可選鏈操作符 ?. 的關系
空值合並運算符針對 undefined 與 null 這兩個值,可選鏈式操作符 ?. 也是如此。可選鏈式操作符,對於訪問屬性可能為 undefined 與 null 的對象時非常有用。
interface Customer {
name: string;
city?: string;
}
let customer: Customer = {
name: "Semlinker"
};
let customerCity = customer?.city ?? "Unknown city";
console.log(customerCity); // 輸出:Unknown city
?:
可選屬性
在面向對象語言中,接口是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類去實現。 TypeScript 中的接口是一個非常靈活的概念,除了可用於對類的一部分行為進行抽象以外,也常用於對「對象的形狀(Shape)」進行描述。
&
運算符
在 TypeScript 中交叉類型是將多個類型合並為一個類型。通過 & 運算符可以將現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
let point: Point = {
x: 1,
y: 1
}
|
分隔符
在 TypeScript 中聯合類型(Union Types)表示取值可以為多種類型中的一種,聯合類型使用 | 分隔每個類型。聯合類型通常與 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => { /* ... */ };
類型保護 in
關鍵字
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);
}
}
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}'.`);
}
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'
}
TypeScript
斷言 <Type>
通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。類型斷言好比其他語言里的類型轉換,但是不進行特殊的數據檢查和解構。它沒有運行時的影響,只是在編譯階段起作用。
“尖括號” 語法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
as 語法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
TypeScript 泛型
參考上面的圖片,當我們調用 identity
其中 T 代表 Type,在定義泛型時通常用作第一個類型變量名稱。但實際上 T 可以用任何有效名稱代替。除了 T 之外,以下是常見泛型變量代表的意思:
K(Key):表示對象中的鍵類型;
V(Value):表示對象中的值類型;
E(Element):表示元素類型。
其實並不是只能定義一個類型變量,我們可以引入希望定義的任何數量的類型變量。比如我們引入一個新的類型變量 U,用於擴展我們定義的 identity 函數:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
@XXX 裝飾器 裝飾器語法
@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 {}
類裝飾器聲明:
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
屬性裝飾器
declare type PropertyDecorator = (target:Object,
propertyKey: string | symbol ) => void;
案例
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";
方法裝飾器
function LogOutput(tarage: Function, key: string, descriptor: any) {
let originalMethod = descriptor.value;
let newMethod = function(...args: any[]): any {
let result: any = originalMethod.apply(this, args);
if(!this.loggedOutput) {
this.loggedOutput = new Array<any>();
}
this.loggedOutput.push({
method: key,
parameters: args,
output: result,
timestamp: new Date()
});
return result;
};
descriptor.value = newMethod;
}
class Calculator {
@LogOutput
double (num: number): number {
return num * 2;
}
}
let calc = new Calculator();
calc.double(11);
// console ouput: [{method: "double", output: 22, ...}]
console.log(calc.loggedOutput);
參 數裝飾器
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;
}
}