Angular 學習筆記 (Typescript 高級篇)


由於 typescript 越來越復雜. 所以特意開多一個篇幅來記入一些比較難的, 和一些到了一定程度需要知道的基礎.

主要參考

https://basarat.gitbook.io/typescript/ 高級書

https://jkchao.github.io/typescript-book-chinese/ 高級書中文版

版本 feature

字節前端的 typescript (它掘金幾篇都寫的不錯)

字節前端的 typescript (它掘金幾篇都寫的不錯)

 

 

1. 名詞術語

Basic Annotation 是基本注解 let a : string

Inline Type Annotation 是內聯類型注解 let a : { name: string }

Union 是聯合類型 string | number

intersection 是交叉類型 string & number

Tuple 是 元組類型[string, number]

Nullish Coalescing 是 ??

Optional Chaining 是可選鏈 obj?.name

Rest parameter 是  call(...args : string[])

Spread operator 是 call(...['a', 'b']) 

Conditional 是 T extends string ? number : never;

Type Guard 是 類型保護 arg is Foo

Destructuring 是解構 const { name } = { name: 'Derrick' }

Naked type 是 type Naked<T> = T extends ... (沒有被任何東西包裝)

NotNaked type 是 type NotNaked<T> = { o: T } extends ... // 在對象里面, 算被抱着了

Utility Types 是 Partial, Required, 等這些東西

Mapped types 是 { [P in TKeys] : any }

object literals 是 { name: string } 

 

2. Number Enums as flags

flags 這個概念 c# 也有 (點這里這里), 經常看到 enum 可以配合 | 來用, 比如 : 

PropertyInfo[] attrs = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); //獲取attrs

簡單理解就是把一個 enum 變成 類似 enum list

typescript 也允許我們做到這點. 點這里

首先定義 enum 的時候需要多寫一些號碼和符號進去進去 (這里用到的是二進制的移位方法, 我不太懂)

enum Abc {
    A = 1,
    B = 1 << 0,
    C = 1 << 1,
    D = 1 << 2,
    E = 1 << 3
}

然后呢,就可以這樣子去使用了. 

let x: Abc = Abc.A | Abc.B;
if(x & Abc.B) {
    console.log('has B');
}
const hasB = !!(x & Abc.B); // true
const hasC = !!(x & Abc.C); // false

書里有寫它的原理,和如果添加移除,但我目前沒有使用到,所以就不研究了. 

 

2.1 擴展特定 enum 方法

通過 namespace

enum Abc {
    A
}
namespace Abc {
    export function print(): string {
        return 'dada';
    }
}
Abc.print()

 

3. 理解 target 和 lib 點這里

target es6 的話, lib 就會有 promise 這些東西.

有些時候我們想分開來管理. 

target 指的是 output 出來的 js 等級

比如 es5, 那么它就不會有 Promise, Set, Map 之類的.

lib 是我們開發環境用的. 我們當然希望什么都有咯.

所以一個比較合理的做法就是,

target es5 出來的 js 是 es5

lib dom, es6, 開發的時候我們用的 es6 的 js 

然后通過 Polyfill 來補上就好了.

 

4. declare overload method 點這里

沒有 overload 的情況下可以這樣 declare, 或者用 interface 也可以

type LongHand = {
  (a: number): number;
};
type ShortHand = (a: number) => number;

overload 情況下只可以用第一種

type LongHandAllowsOverloadDeclarations = {
  (a: number): number;
  (a: string): string;
};

 

5. 兼容性和變體 點這里

兼容性是指一個類型是否可以當另一個類型來使用 (有時候過多的限制會讓代碼很難寫,所以哪怕是強類型我們也經常會要強轉之類的, 所以不可以檔死了)

對象結構 

type IsExtends<T, U> = T extends B ? true : false;
type A = { age: number };
type B = { age: number, name: string }; // 屬性可以多, 不可以少
type result1 = IsExtends<B, A>; // true

函數參數

type Method = (age: number) => void;
type Method2 = () => void; // 參數可以少, 不可以多
type result2 = IsExtends<Method2, Method>; // true

 

上面都是比較簡單的例子,只是數量上的問題

復雜的情況是參數類型,返回類型這些. 

比如 2 個方法對比, 第 1 個參數類型是子類, 第 2 個是父類, 這個能兼容嗎 ?

說到這里可能就需要引入變體的概念了

點這里

首先講一下定義

A ≼ B 意味着 A 是 B 的子類型。

A → B 指的是以 A 為參數類型,以 B 為返回值類型的函數類型。

協變(Covariant):只在同一個方向;

逆變(Contravariant):只在相反的方向;

雙向協變(Bivariant):包括同一個方向和不同方向 (通常這個是不安全的,但有時候沒有辦法就比如強轉一樣)

不變(Invariant):如果類型不完全相同,則它們是不兼容的。

來一個函數類型的例子, 2個函數是否可兼容依據參數類型和返回類型 

灰狗 ≼ 狗 ≼ 動物

有一個方法是 狗 → 狗 

那么怎樣的方法是和它兼容的 ? 

1. 灰狗 → 灰狗

2. 灰狗 → 動物

3. 動物 → 動物

4. 動物 → 灰狗 (正解)

參數類型默認情況下是雙向的 (開啟 strictFunctionTypes 之后就換成逆變),也就是說,只要是父類或者子類都 ok,返回類型是協變的,所以 a extends b 的話, a 的返回子類是 ok 的.

沒開啟 strictFunctionTypes 的時候

type methodParent = (p: Parent) => Parent;
type methodChild = (c: Child) => Child;
type result = IsExtends<methodChild, methodParent>; // true

開啟之后就不行了, 改成父類就 ok 

type methodParent = (p: Parent) => Parent;
type methodChild = (c: Parent) => Child;
type result = IsExtends<methodChild, methodParent>; // true

為什么參數是逆變的, 而返回值是協變的呢 ? 

繼承的概念就是我可以用 B 去替代 A. A 方法可以跑的地方,我全部換成 B 方法,它也要可以跑. 

假設方法 A 的參數是狗 (要求抽象), 那么調用 A 方法的時候就有可能傳入狗或者灰狗 (要求抽象, 但也可以傳入具體).

如果 B 方法的參數是灰狗 (要求具體), 而我拿 B 替換了 A, 當調用的人傳入狗的時候就不 ok 了 (因為 A 只要求抽象,所以調用的人可能傳入抽象, 但是 B 內部卻需要具體, 所以直接翻車).

所以參數是逆變的. (參數需要更抽象才行)

假設返回值 A 是狗 (給予具體), 那么獲取返回值之后使用的就是具體屬性.

如果返回值 B 是動物 (給予更抽象), 那么就翻車了咯

所以返回值是協變的 (返回值要更具體)

協變就是說要求 A 類的地方可以用 A 或 A 的子類來替換 (不可以用 A 的父類), 更具體

逆變就是要求 A 類的地方可以用 A 或 A 的父類來替換 (不可以用 A 的子類). 更抽象

雙向就是上面 2 個都可以

不變就是 A 只能用 A 不可以是其它的

 

6. Conditional, Infer

refer : https://fettblog.eu/typescript-union-to-intersection/ (聯合類型轉交叉類型)

當 contional 遇到 union (Distributive Conditional Types)

如果是 naked 的話會被拆成多個來解析, not naked 的話就是把 T 傳過去用而已

type Naked<T> = T extends any ? { name: T } : never;
type result = Naked<'a' | 'b'>; // { name: 'a' } | { name: 'b' }

type NotNaked<T> = T[] extends any ? { name: T } : never;
type result2 = NotNaked<'a' | 'b'>; // { name: 'a' | 'b' }

Infer 有個特色就是如果 infer 是用在參數類型上, 由於是逆變 position, 最終出來的結果會變成交叉類型.

如果不是逆變的話,出來的結果會是聯合類型

// 這是轉為聯合類型
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>;  // string
type T11 = Foo<{ a: string, b: number }>;  // string | number
// 這是轉為交叉類型
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number

這幾招通常用在轉換 union to array, union to intersection 等

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM