被迫開始學習Typescript —— class

金色海洋(jyk) 發表於 2022-05-16

TS 的 class 看起來和 ES6 的 Class 有點像,基本上差別不大,除了 可以繼承(實現)介面、私有成員、只讀等之外。

參考:https://typescript.bootcss.com/classes.html

基本用法

我們可以定義一個 class,設定幾個屬性,然後設定一個方法,封裝 Object.assign 簡化reactive 的賦值操作。

  • 建立自己的物件基類
  import type { InjectionKey } from 'vue'

  class BaseObject {
    $id: string | symbol | InjectionKey<string>
    name: string
    age: number

    constructor (id: string, name: string, age: number) {
      this.$id = id
      this.name = name
      this.age = age
    }
    
    set $state(value: any) {
      Object.assign(this, value)
    }
  }
  • 使用
  import { reactive, defineComponent } from 'vue'

  const _state = new BaseObject('007', 'jyk')
  const state = reactive(_state)

  state.$state = {
    name: '直接賦值'
  }

看著是不是眼熟?你猜對了!這裡參考 Pinia 設定 $state ,實現給 reactive 直接賦值的功能。

reactive 哪都好,只是整體賦值的時候有點鬱悶,這裡簡單封裝了一下,實現直接賦值的功能。

類的繼承

上面的方法只是封裝了物件,那麼陣列怎麼辦呢?這裡就需要用到“繼承” extends 的用法。

  • 繼承 js 的 Array 建立自己的陣列類
  class BaseArray extends Array  {
    $id: string | symbol | InjectionKey<string>
   
    constructor () {
      // 呼叫父類的 constructor()
      super()
      this.$id = 'array'
    }

    set $state(value: any) {
      this.length = 0
      if (Array.isArray(value)) {
        this.push(...value)
      } else {
        this.push(value)
      }
    }
  }
  • 使用
const _state2 = new BaseArray()
const state2 = reactive(_state2)

state2.$state = [
  {
    name: '008'
  },
  {
    name: '009'
  }
]

這樣陣列形式的 reactive ,也可以直接賦值了,是不是方便很多?

繼承的是原生陣列,所以擁有了陣列的所有功能。
另外,子類的constructor裡面,需要呼叫 super() 才會有 this。

實現介面

觀察上面的兩個 class,會發現擁有相同的成員:$id 和 $state。那麼要不要約束一下?

如果想要實現約束功能的話,可以定義一個 interface 來實現。

  • 定義介面
  interface IState {
    $id: string | symbol | InjectionKey<string>
    set $state(value: any)
  }
  • 實現介面
  class BaseObject implements IState {
    略
  }

  class BaseArray extends Array implements IState {
    略
  }

這樣設定之後,類的成員就要複合介面的定義,不符合的話會出現提示。

私有成員、只讀成員

雖然可以使用 private、readonly 標識私有成員和只讀成員,只是嘛,到目前為止有點雞肋。因為只是在 TS 的範疇內給出錯誤提示,但是完全不影響執行。

那麼能不能變相實現一下呢?可以的,只是有點繞圈圈,另外似乎不太正規。

我們把 $id 改為只讀、偽隱藏成員。

  • 修改一下介面,使用訪問器(get)設定 $id
  interface IState {
    get $id(): string | symbol | InjectionKey<string>
    set $state(value: any)
  }
  • 修改一下物件基類,使用 get 訪問器
  class BaseObject implements IState {
    get $id(): string | symbol | InjectionKey<string>
    略
  }
  • 建立物件例項的函式
  function createState(id: string, name: string, age: number) {
    // 繼承 BaseObject 再定義一個class
    class myState extends BaseObject {
      constructor (name: string, age: number) {
        // 呼叫父類的 constructor()
        super(name, age)
      }
      // 使用 override 覆蓋父類 $id
      override get $id() {
        return id
      }
    }
    
    const _state = new myState(name, age)
    const state = reactive(_state)

    return state
  }
  • 使用
  const state3 = createState('010', 'jyk0013', 29)
  console.log(state3)
  console.log('state3 - keys', Object.keys(state3))
  for (const key in state3) {
    console.log(key, state3[key])
  }
  • 效果

簡單的state.png

  • 分析

把 $id 改為 get 訪問器的方式,可以實現 readonly 的效果。

$id 放在 class (myState) 的“原型”上面,可以避免被遍歷出來,這樣就實現了偽隱藏的效果。

當然 使用 state.$id 的方式還是可以訪問到的,所以是偽隱藏。

完整專案程式碼

https://gitee.com/naturefw-code/nf-rollup-state