1. 程式人生 > >typescript 關於class屬性型別定義被屬性預設值覆蓋的問題及解決方式

typescript 關於class屬性型別定義被屬性預設值覆蓋的問題及解決方式

問題來源於 React.component的第二個引數的型別定義問題,我構建了以下簡化demo,方便描述問題:

class P<STATE> {
    public state: STATE;
}

interface Obj {
    arr: Obj[];
}

class Test1 extends P<Obj> {
    public state = {arr: []};

    func(obj: Obj) {
        this.state.arr.push(obj);// 這裡ts在obj上拋錯  Error:(51, 29) TS2345: Argument of type 'Obj' is not assignable to parameter of type 'never'.
    }
}

這裡主要產生的問題是,我們認為 this.state.arr 應該是Obj[] 型別,所以可以往裡面push進去Obj型別的資料,然而this.state.arr卻被識別為never[]型別,所以push會拋錯。

分析後,發現雖然Test1的state在設定預設值的時候可以使用父類 P 中state的型別定義,但是,在函式中 this.state的型別定義卻是使用 預設值 {arr: []} 的型別推斷。推斷出的型別為{arr:never[]}。

所以產生以上問題。

於是,我們可以這樣處理:

class Test2 extends P<Obj> {
    public state: Obj 
= {arr: []}; // 子類同時定義state型別為Obj func(obj: Obj) { this.state.arr.push(obj); } }

但是這就導致Obj需要定義兩次。

我們又注意到,在建構函式中直接賦值,也可以解決該問題:

class Test3 extends P<Obj> {
    constructor() {
        super();
        this.state = {arr: []}; // 直接在constructor中賦值
    }

    func(obj: Obj) {
        const str: string 
= this.state; this.state.arr.push(obj); } }

但是寫起來是比較麻煩的,每次都要寫建構函式。

 

最後,我們按賦值的思想,考慮使用中間類方式:

// 中間類,繼承P<T> 這裡P類通常為第三方類,如React.Component
abstract class Middle<T> extends P<T> {
    protected constructor() {
        super();
        if (this.initState) {
            this.state = this.initState();
        }
    }

    abstract initState(): T;
}

class Test2 extends Middle<Obj> {
    initState() {// 通過方法來初始化state
        return {arr: []};
    }

    func(obj: Obj) {
        const str: string = this.state;
        this.state.arr.push(obj);
    }
}

我們在中間類中定義了建構函式來預設呼叫initState函式,然後讓state的初始值作為函式的返回值,可以解決屬性預設值的型別覆蓋了父類對屬性引數的定義。

引進來的代價是我們需要一箇中間類。

不過,考慮中間類的引入,可以帶來比較多的擴充套件性,權衡之下,還是可取的。