更新 : 2019-09-15 javascript
Utility Types html
在 typescript 咱們能夠經過一些 "方法" 來改變原有的 type, 變成新的 typejava
這個在 c# 是沒有的. git
先來講說一些 build in 的方法,而後在講講它底層是怎樣製做出來的. github
refer: https://www.typescriptlang.org/docs/handbook/utility-types.htmlajax
1. Partial<T>typescript
Partial 得能力是把 T 的屬性變成 undefined able c#
好比有個接口,promise
interface A {
name: string;
age: number;
}
我想把它變成 app
interface AAA { name?: string | undefined; age?: number | undefined; }
那麼我能夠這樣寫
type AAA = Partial<A>;
常常初始化 class 變量
class Person { constructor(data?: Partial<Person>) { Object.assign(this, data); } name: string; age: number; } const p = new Person({ name: 'keatkeat' });
2. Required<T>
required 和 partial 的功能相反
interface A { name?: string | undefined; }
變成
interface AA {
name: string;
}
interface A {
name: string;
}
變成
interface AA {
readonly name: string;
}
寫法
type AA = Readonly<A>;
4. NonNullable<T>
這裏的 T 不是 class or interface 而是 type
好比
type A = string | number | undefined | null;
變成
type AA = string | number;
寫法是
type AA = NonNullable<A>;
5. ReturnType<T>
當想獲取到 function 的返回類型時就須要這個
class A { method(): string { return 'dada'; } }
type R = ReturnType<A['method']>; // string
若是是單獨的方法要加上 typeof
function Abc() : string { return 'dada'; }
type R2 = ReturnType<typeof Abc>;
6. InstanceType<T>
效果是同樣的.
const spot1: InstanceType<typeof Dog> = new Dog('Spot'); const spot2: Dog = new Dog('Spot Evil Clone');
它的使用場景是用於動態 class, 好比 mixin 或者是 generic
好比
declare function create<T extends new () => any>(c: T): InstanceType<T> class A { } class B { } let a = create(A) // A let b = create(B) // B
7.Record<K,T>
record 的做用是返回一個類型對象, 裏面的 key 就是 K, value type 就是 T
好比我要作一個對象類型, 屬性有 firstname, lastname, fullname, 類型都是 string
那麼能夠這樣寫
type A = Record<'firstname' | 'lastname' | 'fullname', string>
這個例子只是解釋它的功能,真實場景都是配合泛型用的.
8. Pick<T,K>
pick 的做用是從一個對象類型中選擇咱們要的屬性, T 是源對象類型, K 就是指定的 keys 了
class A { name: string; age: number; } type G = Pick<A, 'name'>; type GG = { name: string };
9. Omit<T,K>
omit 和 pick 同樣都是從源對象選出特定的屬性,只不過 omit 的 K 是指不要的屬性和 pick 相反.
10. Extract<T,U> and
Exclude<T,U>
這個和 pick omit 很像,只不過它是用來選擇 keys 輸出 keys 的. pick 和 omit 底層就是用它們實現的啦
type K = 'a' | 'b' | 'c'; type K2 = Extract<K, 'b'>; // pick 提取 type K22 = 'b'; type K3 = Exclude<K, 'b'>; // omit 排除 type K33 = 'a' | 'b';
上面這些 build in 其實都是用更底層的方法實現的.
1. Partial<T>
type MyPartial<T> = { [p in keyof T]? : T[p] };
裏頭有幾個關鍵點,首先是 type MyPartial<T>
它有一個泛型,咱們能夠把它想像成一個方法,經過這個方法能夠製做出動態類型.
這個很很神奇吧,通常靜態語言是沒有這個概念的.
= 的後是一個對象, 意思是經過這個類型能夠建立出一個對象類型.
而後經過 keyof T 把泛型的 keys for loop 放入到這個對象類型中.
屬性的值類型,澤經過 T[p] 來獲取回本來的類型.
經過 ? 來實現把全部的東西變成 undefined.
這就是 Partial 的實現過程.
其它的 build in 基本上也是按照上面這個思路作的。咱們一一來看看.
2. Required
type MyRequired<T> = { [p in keyof T]-? : T[p] };
關鍵是 -?
3. Readonly
type MyRequired<T> = { readonly [p in keyof T] : T[p] };
4. Record
type MyRecord<K extends string | number | symbol, T> = { [p in K] : T };
5.Pick
type MyPick<T, K extends keyof T> = { [p in K] : T[p] };
6.Omit
type MyOmit<T, K extends keyof T> = { [p in Exclude<keyof T, K>] : T[p] };
關鍵是用了 exclude
7. extract 的實現是這樣的
type Extract<T, U> = T extends U ? T : never
用到了一個新的技巧相似 if else
當 T 是 extends U 那麼輸出 T 否則不輸出 (never). 這樣的一個設計就實現了過濾.
exclude 則反過來就好了
type Exclude<T, U> = T extends U ? never : T
咱們只要記得要過濾 keys 就能夠用 if else 的方式就好了.
8. ReturnType 和
InstanceType 更復雜一些
除了用到 if else 也用到了一個新技巧
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
就是 infer R. 懶惰研究下去了. 下次繼續更新吧
綜合就是這幾招啦
TypeFactory<T> = { [P in keyof T] : T[P] }
T extends U ? T : never
(...args: any) => infer R
https://fettblog.eu/typescript-built-in-generics/
http://realfiction.net/2019/02/03/typescript-type-shenanigans-2-specify-at-least-one-property
keyof, never, Pick, Exclude, Record, T in Keys, { }[Keys],
Partial
T extends U ? X : Y,
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
更新 2019-05-31
經常使用的 Pick, Exclude, Omit
refer : https://stackoverflow.com/questions/48215950/exclude-property-from-type
Omit 在 3.5 後是 default 了
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> class Person { name: string; age: number; } type keys = Exclude<keyof Person, 'name'>; const omit: Omit<Person, 'age'> = { name: 'name' } const y: Pick<Person, 'name'> = { name: 'name' }
還有 enum
enum OrderStatus { Pending = 'Pending', WaitForPayment = 'WaitForPayment', WaitForShipping = 'WaitForShipping', Completed = 'Completed', } const orderStatus: Exclude<OrderStatus, OrderStatus.Completed> = OrderStatus.Pending;
更新 2018-12-20
使用 mixin 代碼
假設某些屬性和方法咱們會在多個 component 上覆用.
step 1 : 定義 Constructor 類型
type Constructor<T = {}> = new (...args: any[]) => T;
step 2 : 定義複用的 interface
export interface HaveGetNameMethod {
getName() : string
}
step 3: 定義這個 class 的 constructor
export type HaveGetNameCtor = Constructor<HaveGetNameMethod>;
step 4: 定義這個 class 的依賴 (一般是依賴注入的服務, 這裏隨便寫而已)
export interface HaveGetNameMethodDependency {
name: string
}
step 5: 定義 mixin 方法
function MixinGetName<TBase extends Constructor<HaveGetNameMethodDependency>>(Base: TBase) : TBase & HaveGetNameCtor { return class extends Base { getName() { return this.name; } constructor(...args: any[]) { super(...args); } }; }
傳入的 Base 必須實現依賴, 返回 Base & 這個 class, 這個 class 就是 step 3, 它實現了 step 1 的接口
step 6 : 定義咱們的 base 組件, 必須知足咱們要 exntends 的 class 的依賴
export class BaseComponent {
constructor(
public name: string
){ }
}
step 6 定義咱們要 extends 的 mixin class (這裏能夠接 combo)
export const MixinComponent: HaveGetNameCtor & typeof BaseComponent = mixinGetName(BaseComponent);
它的類型就是全部的 constructor 加起來. a(b(c(d))) <-- 如此的嵌套組合調用.
step 7 最後就是繼承啦
export class TestMixinComponent extends MixinComponent implements OnInit, HaveGetNameMethod { constructor( ) { super('das'); } ngOnInit() { console.log(this.getName()); } }
implement 全部接口, 在 constructor 提供全部依賴. 這樣就能夠啦~
注 :
全部依賴都必須使用 public.
https://github.com/Microsoft/TypeScript/issues/17744
angular aot 有些場景下 life cycle hook 跑步起來哦
https://github.com/angular/angular/issues/19145
更新 2018-05-11
refer : https://blog.mariusschulz.com/2017/05/26/typescript-2-2-mixin-classes
class 動態繼承, Mixin Classes
在寫 Angular 的時候, component class 常常須要一些大衆的功能或者屬性.
要封裝這些方法和屬性,能夠用 2 種方式,一種是 class 繼承, 另外一種是注入另外一個 class
2 個方法各有各的優點.
今天主要說說繼承 Mixin Classes
Material 裏面有很好的實現,你們能夠去看看代碼.
Mixin Classes 是 typescript 的特性之一,比通常的繼承靈活一些.
咱們假設有這樣一個場景.
有 AbstractParentAComponent, ChildAComponent, AbstractParentBComponent, ChildBComponent 4 個組件類
ChildA 繼承 ParentA, ChildB 繼承 ParentB
假如 ChildA 和 ChildB 擁有共同的屬性, 咱們要如何去封裝複用呢?
這就是 Mixin 排上用場的地方
咱們把 A,B 共同點放入 ChildABComponent 類
而後 ChildA extends ChildAB extends ParentA 和 ChildB extends ChildAB extends ParentB
看到了嗎, ChildAB 一下子繼承了 ParentA 一下子又繼承 ParentB,這就是靈活的地方了.
更新 2018-02-04
對於 null and undefined
咱們都知道 null 是一個很奇葩的東西.
好比 :
let a: { name: string } = null; //編輯時經過 console.log(a.name); //運行時報錯
任何一個對象均可以是 null or underfined
因此就有了 a.name 在編輯時不會報錯而在運行時報錯的狀況。
c# 也是這樣的。
雖然咱們碼農對代碼意識很高,幾乎每次都會很天然而然的避開這種錯誤的狀況可是 "說好的編輯時報錯呢 ? "
c# 中咱們會這樣就規避上述的報錯現象
a?.name。這和 angular template 語法是同樣的。表示若是 a 是 null 那麼就返回 null. 這樣運行時獲取的值是 null 也就不會報錯了.
另外一種方法是 typescript 纔有的, c# 沒有. 叫 stricknullcheck = true
當你設置了這個後
let a: { name: string } = null; 在編輯時就報錯了
你必須代表清楚
let a: { name: string } | null = null;
這樣才行。
可是這樣的代交是 a 因爲是 對象或者 null
在智能提示時 a dot 就不會顯示 name 了, 由於它有多是 null 啊
因而 就有了 感嘆號 !
console.log( a!.name );
感嘆號告訴 typescript 這裏的 a 是不可能爲 null or underfined 的。因此就 ok 了
1.接口奇葩驗證
interface Abc { name : string } function abc(obj : Abc) { } let ttc = { name: "adad", age: 12 }; abc(ttc); //no error abc({ name: "adad", age: 12 }); //error
對象字面量會有特別的檢查, 因此一個 no error ,一個 error.
2. readonly
const data: string = "xx"; let obj: { readonly name : string } = { name : "keatkeat" } obj.name = "tata"; //error
const for let, var, readonly for object properties.
3. 初始化對象時賦值 (v2.1)
class Person { constructor(data? : PartialPerson) { Object.assign(this, data); } public name : string } type PartialPerson = Partial<Person>; let person = new Person({ name : "x" }); console.log(person.name);
使用到了 v2.1 的特性 keyof, Partial<T>
4. async await
class Person { ajaxAsync(): Promise<string> { return new Promise<string>((resolve, reject) => { setTimeout(() => { resolve("data"); }, 5000); }); } }
和 c# 相似, c# 中 Task<string> 對應這裏的 Promise<string>
(async function () { let person = new Person(); let data = await person.ajaxAsync(); console.log(data); person.ajaxAsync().then(() => { console.log(data); }); })()
使用也和 c# 同樣, 必須在 async 方法中才可使用 await 關鍵字.
當 await 趕上 Promise 就會有反應了, 固然你也是把它當普通 promise 來使用哦.
捕獲錯誤 :
使用 try catch 來捕獲.
async method() { try { let data = await this.ajaxAsync(); } catch (error) { console.log(error); } }
不用 try catch 的捕獲方式
async method() { let data = await this.ajaxAsync().catch((error) => { console.log(error); return "data"; //if error then data should be ? }); console.log(data); }
ajaxAsync 內部可使用 return Promise.reject("error loh") 或 throw "error loh" 的方式表示出錯.
規則 :
await 關鍵字在 async 方法中才能使用
await 調用的方法 必須是 一個 async method 或則是一個返回 Promise 的方法.
try catch await 3個一塊兒才能捕獲錯誤.
執行順序
class PersonComponent { timeout() : Promise<void> { return new Promise<void>((resolve) => { setTimeout(() => { console.log("2"); resolve(); }, 3000); }); } async ngOnInit() { await this.timeout(); console.log("3"); } } let p = new PersonComponent(); p.ngOnInit(); console.log("1");
因爲沒有使用 await p.ngOnInit() 因此 console.log("1") 優先執行. 而使用了 await 的 ngOnInit 是正常的. 因此即便 ng2 沒使用 await 來調用 ngOnInit 咱們也不用擔憂會有問題^^
容易產生的誤解 : async & await , Promise , rxjs
首先 async await 只是讓咱們的代碼好讀一些, 它也是使用 Promise 來作的.
rxjs 比 promise 靈活, 但不像 promise 簡單理解, 而大部分的時候咱們只須要 promise (async & await), 因此只有當你要用 」流「 的概念時才使用 rxjs
而若是隻是要一個異步方法那麼請使用 async await / promise 就夠了.
5. 擴展原型 extend prototype
refer : http://stackoverflow.com/questions/41192986/extending-the-string-class-doesnt-work-in-some-context
declare global { interface String { test(c: number): string; } interface Array<T> { test(c: number): string; } } String.prototype.test = function (c : number) { return "abc"; } Array.prototype.test = function (c : number) { return ""; } export class Extension { }
而後在 app.module import 它出來就能夠了, 全局定義
import "./@stooges/extension";