angular2 學習筆記 (Typescript)

更新 : 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;
}

 

 

3. Readonly<T> 

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";
相關文章
相關標籤/搜索