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])
}
- 效果
- 分析
把 $id 改為 get 訪問器的方式,可以實現 readonly 的效果。
$id 放在 class (myState) 的“原型”上面,可以避免被遍歷出來,這樣就實現了偽隱藏的效果。
當然 使用 state.$id 的方式還是可以訪問到的,所以是偽隱藏。