05-ts-高級類型

這是我參與更文挑戰的第6天,活動詳情查看: 更文挑戰web


高級類型

交叉類型

交叉類型是將多個類型合併爲一個類型。 咱們能夠把現有的多種類型疊加到一塊兒成爲一種類型,它包含了所需的全部類型的特性。 例如, Person & Serializable & Loggable同時是 Person 和 Serializable 和 Loggable。 就是說這個類型的對象同時擁有了這三種類型的成員。json

咱們大可能是在混入(mixins)或其它不適合典型面向對象模型的地方看到交叉類型的使用。 (在JavaScript裏發生這種狀況的場合不少!) 下面是如何建立混入的一個簡單例子:markdown

function extend<T, U>(first: T, second: U): T & U { // T & U, 至關因而並集是的意思
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

複製代碼

聯合類型

聯合類型與交叉類型頗有關聯,可是使用上卻徹底不一樣。 偶爾你會遇到這種狀況,一個代碼庫但願傳入 number或 string類型的參數。 例以下面的函數:ide

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); // returns " Hello world"
複製代碼

這裏的padding類型是any, 實際咱們須要的是number或者string, 若是傳入的類型不是number或者string, ts也不會報錯。咱們就能夠使用聯合類型, number | string函數

若是一個值是聯合類型,咱們只能訪問此聯合類型的全部類型裏共有的成員。post

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
複製代碼

pet的類型是 Fish | Bird, layEggs是共有的方法,訪問是ok的, 而swim是Fish獨有的, 若是類型是Bird, 就沒有swim方法了,就會報錯。ui

類型保護與區分類型

聯合類型適合於那些值能夠爲不一樣類型的狀況。 但當咱們想確切地瞭解是否爲 Fish時怎麼辦? JavaScript裏經常使用來區分2個可能值的方法是檢查成員是否存在。 如以前說起的,咱們只能訪問聯合類型中共同擁有的成員。url

爲了讓這段代碼工做,咱們要使用類型斷言:spa

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
複製代碼

用戶自定義的類型保護

TypeScript裏的 類型保護機制讓它成爲了現實。 類型保護就是一些表達式,它們會在運行時檢查以確保在某個做用域裏的類型。 要定義一個類型保護,咱們只要簡單地定義一個函數,它的返回值是一個 類型謂詞:3d

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}
複製代碼

在這個例子裏, pet is Fish就是類型謂詞。 謂詞爲 parameterName is Type這種形式, parameterName必須是來自於當前函數簽名裏的一個參數名。

每當使用一些變量調用 isFish時,TypeScript會將變量縮減爲那個具體的類型,只要這個類型與變量的原始類型是兼容的。

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並不會阻止你與其它字符串比較,語言不會把那些表達式識別爲類型保護。

能夠爲null的類型

TypeScript具備兩種特殊的類型, null和 undefined,它們分別具備值null和undefined. 默認狀況下,類型檢查器認爲 null與 undefined能夠賦值給任何類型。 null與 undefined是全部其它類型的一個有效值。 這也意味着,你阻止不了將它們賦值給其它類型,就算是你想要阻止這種狀況也不行。

let s = "foo";
s = null; // 錯誤, 'null'不能賦值給'string'
let sn: string | null = "bar";
sn = null; // 能夠

sn = undefined; // error, 'undefined'不能賦值給'string | null'

複製代碼

可選參數和可選屬性

tsconfig.json使用了 --strictNullChecks,可選參數會被自動地加上 | undefined:

function f(x: number, y?: number) {
    return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
複製代碼

類型保護和類型斷言

因爲能夠爲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:

let obj = {}

obj!.name
複製代碼

類型別名

類型別名會給一個類型起個新名字。 類型別名有時和接口很像,可是能夠做用於原始值,聯合類型,元組以及其它任何你須要手寫的類型。

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 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"); 
複製代碼

Record

ts中,若是要定義一個對象的key,value類型,該怎麼作呢?這時候就須要用到 TS 的 Record 了。

interface Person { name: string; }

type Key = "p1" | "p2" | "p3";

const person: Record<Key, Person> = { p1: { name: "zs" }, p2: { name: "ls" }, p3: { name: "ww" }, }; Record 後面的泛型就是對象 鍵和值的類型。

ReturnType

ReturnType<T> -- 獲取函數返回值類型.

type T10 = ReturnType<() => string>;  // string
type T11 = ReturnType<(s: string) => void>;  // void
type T12 = ReturnType<(<T>() => T)>;  // {}
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;  // number[]
type T15 = ReturnType<any>;  // any
type T16 = ReturnType<never>;  // any
type T17 = ReturnType<string>;  // Error
type T18 = ReturnType<Function>;  // Error

複製代碼
相關文章
相關標籤/搜索