typescript 關於class屬性型別定義被屬性預設值覆蓋的問題及解決方式
阿新 • • 發佈:2019-01-12
問題來源於 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的初始值作為函式的返回值,可以解決屬性預設值的型別覆蓋了父類對屬性引數的定義。
引進來的代價是我們需要一箇中間類。
不過,考慮中間類的引入,可以帶來比較多的擴充套件性,權衡之下,還是可取的。