<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                &emsp;&emsp;本節將對TypeScript中類型的高級特性做詳細講解,包括交叉類型、類型別名、類型保護等。 ## 一、交叉類型 &emsp;&emsp;交叉類型(Intersection Type)是將多個類型通過“&”符號合并成一個新類型,新類型將包含所有類型的特性。例如有Person和Programmer兩個類(如下代碼所示),當man變量的類型聲明為Person&Programmer時,它就能使用兩個類的成員:name屬性和work()方法。 ~~~ class Person { name: string; } class Programmer { work() { } } let man: Person&Programmer; man.name; man.work(); ~~~ &emsp;&emsp;交叉類型常用于混入(mixin)或其它不適合典型面向對象模型的場景,例如在下面的示例中,通過交叉類型讓新對象obj同時包含a和b兩個屬性。 ~~~ function extend<T, U>(first: T, second: U): T & U { const result = <T & U>{}; for (let prop in first) { (<T>result)[prop] = first[prop]; } for (let prop in second) { if (!result.hasOwnProperty(prop)) { (<U>result)[prop] = second[prop]; } } return result; } let obj = extend({ a: 1 }, { b: 2 }); ~~~ ## 二、類型別名 &emsp;&emsp;TypeScript提供了type關鍵字,用于創建類型別名,可作用于基本類型、聯合類型、交叉類型和泛型等任意類型,如下所示。 ~~~ type Name = string; //基本類型 type Func = () => string; //函數 type Union = Name | Func; //聯合類型 type Tuple = [number, number]; //元組 type Generic<T> = { value: T }; //泛型 ~~~ &emsp;&emsp;注意,起別名不是新建一個類型,而是提供一個可讀性更高的名稱。類型別名可在屬性里引用自身,但不能出現在聲明的右側,如下所示。 ~~~ type Tree<T> = { value: T; left: Tree<T>; right: Tree<T>; } type Arrs = Array<Arrs>; //錯誤 ~~~ ## 三、類型保護 &emsp;&emsp;當使用聯合類型時,只能訪問它們的公共成員。假設有一個func()函數,它的參數是由Person和Programmer兩個類組成的聯合類型,如下代碼所示。 ~~~ function func(man: Person | Programmer) { if((<Person>man).run) { (<Person>man).run(); }else { (<Programmer>man).work(); } } ~~~ &emsp;&emsp;雖然利用類型斷言可以確定參數類型,在編譯階段避免了報錯,但是多次調用類型斷言未免過于繁瑣。于是TypeScript就引入了類型保護機制,替代類型斷言。類型保護(Type Guard)是一些表達式,允許在運行時檢查類型,縮小類型范圍。 **1)typeof** &emsp;&emsp;TypeScript可將typeof運算符識別成類型保護,從而就能直接在代碼里檢查類型(如下所示),其計算結果是個字符串,包括“number”、“string”、“boolean”或“symbol”等關鍵字。 ~~~ function send(data: number | string) { if (typeof data === "number") { //... } else if(typeof data === "string") { //... } } ~~~ **2)instanceof** &emsp;&emsp;TypeScript也可將instanceof運算符識別成類型保護,通過構造函數來細化類型,檢測實例和類是否有關聯,如下所示。 ~~~ function work(man: Person | Programmer) { if (man instanceof Person) { //... } else if(man instanceof Programmer) { //... } } ~~~ **3)自定義** &emsp;&emsp;TypeScript還允許自定義類型保護,其形式和函數聲明類似,只是返回類型需要改成類型謂詞,如下所示。 ~~~ function isPerson(man: Person | Programmer): man is Person { return !!(<Person>man).run; } ~~~ &emsp;&emsp;類型謂詞由當前函數的參數名稱、is關鍵字和指定的類型名稱所組成。 ## 四、字面量類型 &emsp;&emsp;TypeScript可將字符串字面量作為一個類型,用于指定一個字符串類型的固定值。當該類型與聯合類型、類型別名等特性配合使用時,可以模擬出枚舉的效果,如下所示。 ~~~ type Direction = "Up" | "Down" | "Left"; function move(data: Direction) { return data; } move("Up"); //正確 move("Right"); //錯誤 ~~~ &emsp;&emsp;move()函數只能接收Direction類型的三個固定值,傳入其它值都會產生錯誤。 &emsp;&emsp;字符串字面量類型還可以用來區分函數重載,如下所示。 ~~~ function run(data: "Left"): string; function run(data: "Down"): string; function run(data: string) { return data; } ~~~ &emsp;&emsp;其它常見的字面量類型還有數字和布爾值,如下所示。 ~~~ type Numbers = 1 | 2 | 3 | 4 | 5 | 6; type Bools = true | false; ~~~ &emsp;&emsp;注意,字面量類型屬于單例類型。單例類型是一種只有一個值的類型,當每個枚舉成員都用字面量初始化時,枚舉成員是具有類型的,叫枚舉成員類型,它也屬于單例類型。 ## 五、可辨析聯合 &emsp;&emsp;通過合并單例類型、聯合類型、類型保護和類型別名可創建一種高級模式:可辨析聯合(Discriminated Union),也叫做標簽聯合或代數數據類型。TypeScript中的可辨析聯合具有3個要素: &emsp;&emsp;(1)具有單例類型的屬性,即可辨析的特征或標簽。 &emsp;&emsp;(2)一個聯合了多個類型的類型別名。 &emsp;&emsp;(3)針對第一個要素中的屬性的類型保護。 &emsp;&emsp;在下面的示例中,首先聲明了兩個接口,每個接口都有字符串字面量類型的kind屬性,并且其值都不同,而kind屬性就是第一個要素中的可辨析的特征或標簽。 ~~~ interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; } ~~~ &emsp;&emsp;然后將兩個接口聯合,并創建一個類型別名,實現第二個要素,如下所示。 ~~~ type Shape = Rectangle | Circle; ~~~ &emsp;&emsp;最后通過具有判斷性的kind屬性,結合switch語句,執行類型保護,縮小類型范圍,如下所示。 ~~~ function caculate(s: Shape) { switch (s.kind) { case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } } ~~~ **1)完整性檢查** &emsp;&emsp;當未涵蓋可辨析聯合的所有變化時,需要能反饋到編譯器中。例如新增Square接口,并將它添加到Shape類型中(如下所示),如果未更新caculate()函數,那么就不能編譯通過。 ~~~ interface Square { kind: "square"; size: number; } type Shape = Rectangle | Circle | Square; ~~~ &emsp;&emsp;有兩種方法能實現這種預警,第一種是在輸入編譯命令時添加--strictNullChecks參數,并為caculate()函數指定返回值類型,如下所示。 ~~~ function caculate(s: Shape): number { switch (s.kind) { case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } } ~~~ &emsp;&emsp;由于switch語句沒有包含所有類型,因此TypeScript會認為該函數有可能返回undefined,從而就會編譯報錯。注意,這種方法不太精確,有很多因素(例如函數默認返回數字)會干擾完整性檢查,并且--strictNullChecks參數對舊代碼有兼容問題。 &emsp;&emsp;第二種方法是使用never類型,如下代碼所示,新增一個能引發類型錯誤的assertNever()函數,并在default分支中調用該函數。 ~~~ function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } function caculate(s: Shape) { switch (s.kind) { case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; default: return assertNever(s); } } ~~~ &emsp;&emsp;雖然額外定義了一個函數,但是檢查的精確度提升了不少。 ## 六、索引類型 &emsp;&emsp;索引類型(Index Type)能讓編譯器檢查使用動態屬性的場景,例如從對象中選取屬性的子集,如下所示。 ~~~ function pluck(obj, names) { return names.map(n => obj[n]); } ~~~ &emsp;&emsp;如果要讓pluck()函數能從obj對象中成功的選出names數組所指定的屬性,那么需要在聲明時設置類型約束,包括names中的元素必須是obj中存在的屬性以及返回值類型得是obj屬性值的類型,下面通過泛型來描述這些約束。 ~~~ function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] { return names.map(n => obj[n]); } interface Person { name: string; age: number; } let person: Person = { name: "strick", age: 28 }; let attrs: string[] = pluck(person, ["name"]); ~~~ &emsp;&emsp;泛型函數pluck()引入了兩個新的類型操作符,分別是索引類型查詢操作符(keyof T)和索引訪問操作符(T\[K\])。前者會取T類型中由公共(public)屬性名所組成的聯合類型,例如“"name" | "age"”;后者會取T類型中指定屬性值的類型,這意味著示例中的person\["name"\]和Person\["name"\]兩者的類型都是string。 **1)字符串索引簽名** &emsp;&emsp;keyof T與T\[K\]同樣適用于字符串索引簽名,以下面的泛型接口People為例,kType的類型是string和number的聯合類型,因為JavaScript里的數值索引會自動轉換成字符串索引;vType的類型是number,也就是索引簽名的類型。 ~~~ interface People<T> { [key: string]: T; } let kType: keyof People<number>; //string | number let vType: People<number>["name"]; //number ~~~ ## 七、映射類型 &emsp;&emsp;映射類型(Mapped Type)與索引類型類似,也是從現有類型中創建出一種新類型。接下來用一個例子來演示映射類型用法,假設有一個Person接口,它有兩個成員,如下所示。 ~~~ interface Person { name: string; age: number; } ~~~ &emsp;&emsp;當需要將Person接口的每個成員都變為可選或只讀的,粗糙的解決方法是一個個的修改,如下所示。 ~~~ interface PersonPartial { name?: string; age?: number; } interface PersonReadonly { readonly name: string; readonly age: number; } ~~~ &emsp;&emsp;而如果采用映射類型,那么就能快速的改變接口成員,如下代碼所示,其中Readonly可將T類型的成員設為只讀,而Partial會將它們設為可選。 ~~~ type Readonly<T> = { readonly [P in keyof T]: T[P]; } type Partial<T> = { [P in keyof T]?: T[P]; } type PersonPartial = Partial<Person>; type ReadonlyPerson = Readonly<Person>; ~~~ &emsp;&emsp;\[P in keyof T\]的語法與索引類型的類似,但內部使用了for-in遍歷語句,其中: &emsp;&emsp;(1)P是類型變量,會依次綁定到每個成員上,對應成員名的類型。 &emsp;&emsp;(2)T是由字符串字面量構成的聯合類型,表示一組成員名,例如“"name" | "age"”。 &emsp;&emsp;(3)T\[P\]是成員值的類型。 &emsp;&emsp;注意,映射類型描述的是類型而非成員,如果要添加額外的成員,需要使用交叉類型的方式,如下所示,直接在類型中添加成員會無法通過編譯。 ~~~ //交叉類型 type ReadonlyNew<T> = { readonly [P in keyof T]: T[P]; } & { data: boolean }; //編譯錯誤 type ReadonlyNew<T> = { readonly [P in keyof T]: T[P]; data: boolean; }; ~~~ &emsp;&emsp;Readonly和Partial是一種同態轉換,即在映射時保留源類型的成員名以及其值類型,并且與目標類型相比只有修飾符有差異。而那些會創建新成員、改變成員類型或其值類型的轉換都被稱為非同態。由于Readonly和Partial很實用,因此它們已經被包含進TypeScript的標準庫里,作為內置的工具類型存在。 ## 八、條件類型 &emsp;&emsp;條件類型(Conditional Type)能夠表示非統一的類型映射,常以條件表達式進行類型檢測,語法類似于三目運算符,從兩個類型中選出一個,如下所示。 ~~~ T extends U ? X : Y ~~~ &emsp;&emsp;如果T是U的子類型,那么類型將被解析成X,否則是Y。當條件的真假無法確定時,得到的結果將是由X和Y組成的聯合類型,以下面的全局函數sum()為例,T是布爾值的子類型,當傳入的參數是true時,得到的將是string類型;而傳入false時,得到的是number類型。 ~~~ declare function sum<T extends boolean>(x: T): T extends true ? string : number; let x = sum(true); //string | number ~~~ &emsp;&emsp;如果T或U包含類型變量,那么就得延遲解析,即等到類型變量都有具體類型后才能計算出條件類型的結果。在下面的示例中,創建了一個Person接口,聲明的全局函數add()的返回值類型會根據是否是Person的子類型而改變,并且在泛型函數func()中調用了add()函數。 ~~~ interface Person { name: string; age: number; getName(): string; } declare function add<T>(x: T): T extends Person ? string : number; function func<U>(x: U) { let a = add(x); let b: string | number = a; } ~~~ &emsp;&emsp;雖然a變量的類型尚不確定,但是條件類型的結果不是string就是number,因此可以成功的賦給b變量。 **1)分布式條件類型** &emsp;&emsp;當條件類型中被檢查的類型是無類型參數(naked type parameter)時,它會被稱為分布式條件類型(Distributive Conditional Type)。其特殊之處在于它能自動分布聯合類型,舉個簡單的例子,假設T的類型是A | B | C,那么它會被解析成三個條件分支,如下所示。 ~~~ (A | B | C) extends U ? X : Y //等同于 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y) ~~~ &emsp;&emsp;分布式條件類型可以用來過濾聯合類型,如下所示,Filter類型可從T中移除U的子類型。 ~~~ type Filter<T, U> = T extends U ? never : T; type T1 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" type T2 = Filter<string | number | (() => void), Function>; // string | number ~~~ &emsp;&emsp;分布式條件類型也可與映射類型配合使用,進行針對性的類型映射,即不同源類型對應不同映射規則,例如映射接口的方法名,如下所示。 ~~~ type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]; type T3 = FunctionPropertyNames<Person>; // "getName" ~~~ &emsp;&emsp;注意,條件類型與聯合類型和交叉類型相似,不允許遞歸地引用自身,下面這樣寫會在編譯階段報錯。 ~~~ type Custom<T> = T extends any[] ? Custom<T[number]> : T; ~~~ **2)類型推斷** &emsp;&emsp;在條件類型的extends子句中,允許通過infer聲明引入一個待推斷的類型變量,并且可出現多個同類型的infer聲明,例如用infer聲明來提取函數的返回值類型,如下所示。有一點要注意,只能在true分支中使用infer聲明的類型變量。 ~~~ type Func<T> = T extends (...args: any[]) => infer R ? R : any; ~~~ &emsp;&emsp;當函數具有重載時,就取最后一個函數簽名進行推斷,如下所示,其中ReturnType是內置的條件類型,可獲取函數類型T的返回值類型。 ~~~ declare function load(x: string): number; declare function load(x: number): string; declare function load(x: string | number): string | number; type T4 = ReturnType<typeof load>; // string | number ~~~ &emsp;&emsp;注意,無法在正常類型參數的約束子語句中使用infer聲明,如下所示。 ~~~ type Func<T extends (...args: any[]) => infer R> = R; ~~~ &emsp;&emsp;但是可以將約束里的類型變量移除,并將其轉移到條件類型中,就能達到相同的效果,如下所示。 ~~~ type AnyFunction = (...args: any[]) => any; type Func<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any; ~~~ **3)預定義的條件類型** &emsp;&emsp;除了之前示例中用到的ReturnType之外,TypeScript還預定義了4個其它功能的條件類型,如下所列。 &emsp;&emsp;(1)Exclude:從T中移除掉U的子類型。 &emsp;&emsp;(2)Extract:從T中篩選出U的子類型。 &emsp;&emsp;(3)NonNullable:從T中移除null與undefined。 &emsp;&emsp;(4)InstanceType:獲取構造函數的實例類型。 ~~~ type T11 = Exclude<"a" | "b" | "c" | "d", "a" | "c">; // "b" | "d" type T12 = Extract<"a" | "b" | "c" | "d", "a" | "c">; // "a" | "c" type T13 = NonNullable<string | number | undefined>; // string | number type T14 = ReturnType<(s: string) => void>; // void class Programmer { name: string; } type T15 = InstanceType<typeof Programmer>; //Programmer ~~~ ***** > 原文出處: [博客園-TypeScript躬行記](https://www.cnblogs.com/strick/category/1561745.html) [知乎專欄-TypeScript躬行記](https://zhuanlan.zhihu.com/pwts2019) 已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看