交叉類型
把多個類型合并成一個大而全的類型, 運算符為 &
interface obj1 {
name: string;
age: number;
}
interface obj2 {
addr: string,
fav: string[]
}
type obj = obj1 & obj2
/**
* {
* name: string
* age: number
* addr: string
* fav: string[]
* }
*/
聯合類型
多個類型的"或"關系, 表示一個值可以是幾種類型之一, 運算符為 |
type UnionType = string | number
const num: UnionType = 123 // OK
const str: UnionType = 'I am string' // OK
類型保護
why: 聯合類型帶了一個問題, 即我們只能訪問此聯合類型的所有類型里共有的成員
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
這很好理解, 因為不知道用戶調用時會用到具體哪個類型,所以規定只能使用共同成員.
你可能會說我們可以用 if
條件判斷啊. 是的,可以!
let pet = getSmallPet();
// 每一個成員訪問都會報錯
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
正確姿勢是使用類型斷言:
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
既然有了 if
的類型判斷, else
里面是不是可以省略了? 這就用到類型保護.
自定義類型保護
類型保護就是一些表達式, 它們會在運行時檢查以確保在某個作用域里的類型. 要定義一個類型保護, 我們只要簡單地定義一個函數, 的返回值是一個 類型謂詞:
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
在這個例子里, pet is Fish
就是類型謂詞. 謂詞為 parameterName is Type
這種形式, parameterName
必須是來自于當前函數簽名里的一個參數名.
// 'swim' 和 'fly' 調用都沒有問題了
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
typeof
類型保護
類型保護雖然香, 但要多寫一個判斷函數太費力了, 況且我們平時用到的聯合類型大多是基本數據類型. 幸運的是, 現在我們不必將 typeof x === "number"
抽象成一個函數, 因為TypeScript
可以將它識別為一個類型保護.
function getLength(value: string | number): number {
if (typeof value === 'string') return value.length
else return value.toString().length
}
這些 typeof
類型保護只有兩種形式能被識別:
typeof v === "typename"
typeof v !== "typename"
typename
必須是
number
string
boolean
symbol
但是TypeScript
并不會阻止你與其它字符串比較, 語言不會把那些表達式識別為類型保護.
instanceof
類型保護
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;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 類型為SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 類型細化為'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 類型細化為'StringPadder'
}
null
類型保護
由于可以為 null
的類型是通過聯合類型實現, 那么你需要使用類型保護來去除 null
. 幸運地是這與在JavaScript
里寫的代碼一致:
function f(sn: string | null): string {
if (sn == null) {
return "default";
} else {
return sn;
}
}
這里很明顯地去除了 null
, 你也可以使用短路運算符:
function f(sn: string | null): string {
return sn || "default";
}
如果編譯器不能夠去除 null
或 undefined
, 你可以使用類型斷言手動去除. 語法是添加 !
后綴: identifier!
從 identifier
的類型里去除了 null
和 undefined
:
// ts報錯
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
// 修改后
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
枚舉成員類型
當每個枚舉成員都是用字面量初始化的時候枚舉成員是具有類型的.
索引類型
通過 索引類型查詢 和 索引訪問操作符
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]
keyof T
索引類型查詢操作符
對于任何類型 T
, keyof T
的結果為 T
上已知的公共屬性名的聯合. 例如:
let personProps: keyof Person; // 'name' | 'age'
T[K]
索引訪問操作符
這里, 類型語法反映了表達式語法. 這意味著 person['name']
具有類型 Person['name']
— 在我們的例子里則為 string
類型. 然而, 就像索引類型查詢一樣, 你可以在普通的上下文里使用 T[K]
, 這正是它的強大所在. 你只要確保類型變量 K extends keyof T
就可以了. 例如下面 getProperty
函數的例子:
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name]; // o[name] is of type T[K]
}
getProperty
里的 o: T
和 name: K
, 意味著 o[name]: T[K]
. 當你返回 T[K]
的結果, 編譯器會實例化鍵的真實類型, 因此 getProperty
的返回值類型會隨著你需要的屬性改變。
let name: string = getProperty(person, 'name');
let age: number = getProperty(person, 'age');
let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age'
索引類型和字符串索引簽名
keyof
和 T[K]
與字符串索引簽名進行交互. 如果你有一個帶有字符串索引簽名的類型, 那么 keyof T
會是 string
. 并且 T[string]
為索引簽名的類型:
interface Map<T> {
[key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number
類型映射
見之前總結的 "內置類型", 是同一個概念