網站搜尋

如何建立一個使用物件定義類別欄位的 TypeScript 類別建構子?


在 TypeScript 中,可以創建一個帶有帶有存取修飾符的參數的構造函數的類,並且它會自動將這些參數轉換為類的欄位。

class Item {
  constructor(
    public id: number,
    public updatedAt: number,
    public createdAt: number,
  ) {}
}

const item = new Item(1, 1, 1);
item.id // 1

我想知道是否有一種方法可以改為將所有這些參數放在一個物件中傳遞

class Item {
  constructor({
    public id: number,
    public updatedAt: number,
    public createdAt: number,
  }) {}
}

const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
item.id // 1

這可能嗎?有一天會可能嗎?

有沒有可以做類似事情的變通方法?

答案#1

最簡單的方法是先在類別中宣告欄位,並使用映射型別作為參數,然後使用 Object.assign 將欄位指派給 this。我們有幾種選擇可以決定使用哪個映射型別:

Partial<T>

型別將包含類別的所有成員(欄位和方法),但它們都是可選的。這裡的缺點是我們無法將某些欄位設為必填,並且呼叫者可能會覆蓋方法。

class Item {

    public id: number;
    public updatedAt: number;
    public createdAt: number;
    constructor(data: Partial<Item>) {
        Object.assign(this, data);
    }
    method() {}
}

//Works 
const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
//This also works unfortunately 
const item2 = new Item({ id: 1, method() { console.log('overriden from param !')} });

Pick<T, K>

這個映射類型允許我們通過指定幾個 T 的鍵的字串字面類型聯合,從 T 中選擇一些屬性。其優點是 Pick 會繼承類別中原始聲明的屬性是否為必填(因此某些屬性可以是必填,而其他屬性可選),並且由於我們明確指定了要挑選的成員,因此可以省略方法。缺點是我們必須將屬性名稱寫兩次(一次在類別中,一次在 Pick 中):

class Item {
    public id: number;
    public updatedAt?: number;
    public createdAt?: number;
    constructor(data: Pick<Item, "id" | "updatedAt" | "createdAt">) {
        Object.assign(this, data);
    }
    method() {}
}
const item = new Item({ id: 1  }); //id is required others fields are not
const item2 = new Item({ id: 1, method() {}  }); // error method is not allowed

自訂映射型別來移除方法

第三個選項是創建一種類似於 Pick 的型別,但自動包含所有類別欄位而不包含方法。我們可以在 Typescript 2.8 使用條件型別來做到這一點(在撰寫本文時尚未釋出,但應該會在 2018 年 3 月釋出,你現在可以透過 npm install -g typescript@next 立即獲取)。這具有 Pick 的優點,而不需要再次指定欄位名稱:

type NonMethodKeys<T> = {[P in keyof T]: T[P] extends Function ? never : P }[keyof T];  
type RemoveMethods<T> = Pick<T, NonMethodKeys<T>>; 

class Item {
    public id!: number;
    public updatedAt?: number;
    public createdAt?: number;
    constructor(data: RemoveMethods<Item>) { // No need to specify field names again
        Object.assign(this, data);
    }
    method() {}
}

const item = new Item({ id: 1  });  //id is required others fields are not
const item2 = new Item({ id: 1, method() {}  }); // error method is not allowed 

遊樂場連結