交叉類型,就是將多個類型合併爲一個新的類型,這個新的類型具備這多個類型的成員,含有這幾個類型的全部特性,是他們的綜合體,像是集合的並集dom
例子:函數
function extend<T,U>(first: T, second: U): T&U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = first[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = second[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class myLoggable implements Loggable { log() { console.log('qwe'); } } let jim = extend(new Person('qq'), new myLoggable()); console.log(jim.name); jim.log();
例子中jim有Person中的name屬性也有myLoggable中的log()方法this
聯合類型,不像是交叉類型是多個類型的合集,表示是這多個類型中的一種類型,像是集合中的交集,只有多個類型中共有的特性才能夠被調用url
例如要向一個函數傳遞參數,這個參數多是number也多是stringspa
function padLeft(value: string, padding: any) { 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}'.`); } padLeft("Hello world", 4); // " Hello world"
這裏存在一個問題,將padding定義爲any,表示咱們能夠傳遞任何值給padding,這個錯誤TypeScript是不會報錯的,只有在編譯時纔會報錯code
解決這個問題,能夠採用聯合類型,用豎線分割每一個類型,表示是這幾個類型中的一種對象
function padLeft(value: string, padding: string | number) { ........ } let f = padLeft("Hello world", true); // error
若是一個值是聯合類型,就只能訪問這多個類型所共有的成員blog
interface Bird { fly(); layEggs(); } interface Fish { swim(); layEggs(); } function getSmallPet(): Fish | Bird { ... } let pet = getSmallPet(); pet.layEggs(); // okay pet.swim(); // errors
getSmallPet的返回類型就是一個聯合類型,因此pet就只能訪問Bird和Fish的共有成員layEggs()接口
在上面的例子中,咱們不知道pet究竟是那種類型,因此就不可能去訪問哪些不是公共的成員,若是咱們知道了pet的類型,就能夠訪問該類型的全部成員了ip
爲了解決上面提到的具體類型的肯定問題,須要引入類型斷言(類型轉換)
let pet = getSmallPet(); if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); }
問題顯而易見,每次都須要對pet進行類型轉換,麻煩
類型保護就能夠解決上述每次都要進行類型斷言的缺點,類型保護就是一些表達式,它們會在運行時檢查以確保在某個做用域裏的類型。 要定義一個類型保護,咱們只要簡單地定義一個函數,它的返回值是一個類型斷言
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(); }
TypeScript不只知道在if裏是Fish,並且還知道在else裏是Bird類型
咱們將以前的padLeft代碼改用類型斷言來實現
function isNumber(x: any): x is number { return typeof x === "number"; } function isString(x: any): x is string { return typeof x === "string"; } function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value; } if (isString(padding)) { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
若是要按這樣寫就要爲每一個原始類型寫一個函數,麻煩。TypeScript會把"typeof v === typeofname"和"typeof v !== typeofname"看作是類型保護,因此就沒必要爲一個原始類型寫一個函數,直接用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; } } 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' }
類型別名就是給類型起一個別名,並且能夠用於基礎的數據類型
type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
與接口不一樣,類型別名不會新建一個類型,只是類型的名字變了而已
type Alias = { num: number } interface Interface { num: number; } declare function aliased(arg: Alias): Alias; declare function interfaced(arg: Interface): Interface;
在上面的代碼中interfaced返回值的類型是Interface,而aliased返回值的類型是對象字面量
類型別名與接口還有一個地方不一樣的是類型別名不能夠被extends和implements
上面說了類型別名與接口的兩個不一樣點,固然也有相同點,即均可以使用泛型
type Tree<T> = { value: T; left: Tree<T>; right: Tree<T>; }
字符串字面量類型能夠與聯合類型,類型保護和類型別名很好的配合
type Easing = "ease-in" | "ease-out" | "ease-in-out"; class UIElement { animate(dx: number, dy: number, easing: Easing) { if (easing === "ease-in") { // ... } else if (easing === "ease-out") { } else if (easing === "ease-in-out") { } else { // error! should not pass null or undefined. } } } let button = new UIElement(); button.animate(0, 0, "ease-in"); button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
只能從規定的三種類型中選擇一種類型進行傳遞,其餘的類型會報錯
能夠合併字符串字面量類型,聯合類型,類型保護和類型別名來建立一個叫作可辨識聯合的高級模式
先定義三個將要聯合的接口,每一個接口都有kind屬性,可是值不一樣,king屬性能夠做爲可辨識的特徵和標識
interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; }
而後將他們聯合到一塊兒
type Shape = Square | Rectangle | Circle;
使用可辨識聯合
function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } }
注意:若是在Shape中添加新的類型,那麼在switch下也要添加相應的判斷
參考資料: