基于Vue3做一套适合自己的状态管理(二)继承:充血实体类
计划章节
- 基类:实现辅助功能
- 继承:充血实体类
- 继承:Option 风格的状态
- Model:正确的打开方式
- 组合:setup 风格,更灵活
- 注册状态的方式和状态的有效范围
- 列表页面需要的状态
- 当前登录用户的状态
继承:使用充血实体类的方式实现状态
上一篇实现了一个基类(BaseObject),实现了几个辅助功能($state、$patch等),基类在初始化的时候,依据传入的参数创建属性。实现辅助功能之后,继续发展出现了分支:
- 继承:充血实体类的风格,一层一层往下继承(派生)。
- 组合:compositionAPI的风格,组合代替继承。
结合实际需求,我觉得两个都可以有,继承适合简单的状态,组合适合复杂的需求。
继承出来一个新状态
我们如果要实现一个 person 的状态,可以继承 BaseObject 定义一个子类:
class Person extends BaseObject {
name: string
age: number
constructor() {
super({})
// 设置属性
this.name = '子类的名字'
this.age = 18
}
// 设置方法
add() {
this.age += 1
}
}
继承 BaseObject 可以得到辅助功能,然后设置自己的属性和方法。
然后就是实现响应性的问题了,可以有两种方法:
- 使用 ref:像 Pinia 那样,每一个属性都是 ref 的形式;
- 使用 reactive:class 的实例套上 reactive。
使用 ref 实现响应性
我们可以在初始化的时候使用ref,当然也可以使用 computed:
import { reactive, computed, toRaw, ref } from 'vue'
import type { ComputedRef, Ref } from 'vue'
class Person extends BaseObject {
name: Ref<string>
age: Ref<number>
testComputed: ComputedRef
constructor() {
super({})
// 设置属性,直接使用 ref
this.name = ref('ref')
this.age = ref(20)
// 直接使用 computed
this.testComputed = computed(() => {
return this.name.value + '测试 computed'
})
}
// 设置方法,action
add() {
this.age.value += 1
}
}
这样我们就实现了响应性。其实,同理,属性也可以使用 reactive、readonly
看看结构
层次非常分明,第一层是属性和getter,第二层是action,第三层是辅助函数。 这里的 getter 是一个类型是computed 的属性。
使用 reactive 实现响应性
Pinia会把每一个属性都变成 ref 的形式,我个人不太喜欢,感觉有点浪费,为啥不直接使用reactive?
在初始化的时候,属性使用原生的 js 类型,然后在 new 的时候套上 reactive ,感觉这样效率可以更高一些:
class Person2 extends BaseObject {
#__getters: {[key: string]: ComputedRef}
name: string
age: number
constructor() {
super({})
this.#cp = {}
// 设置属性
this.name = '子类的名字'
this.age = 18
}
// 设置 computed
get getName() {
const re = toRaw(this).#__getters['getName']
if (re){
return re
} else {
return toRaw(this).#__getters['getName'] = computed(() => {
return this.name + '--测试 computed'
})
}
}
// 设置方法
add() {
this.age += 1
}
}
// 创建实例
const person = new Person()
// 套上 reactive
const nfState = reactive(person)
ref 和 reactive 各有优势,大家喜欢哪种方式呢?
神奇的 this 指向
看了上面 reactive 的方式,可能你会觉得奇怪,这样写(add、getName)会有响应性吗?——会的,因为this的指向是一件很神奇的事情。
初始化的时候 this 指向的是普通的对象(class),这时候没有响应性,所以初始化的时候不能直接设置computed,除非属性直接使用 ref,或者reactive,但是这样就变成第一种方式了。
但是创建的实例,套上 reactive 之后, this 就会指向 reactive (Proxy)。这样在 add 里面使用 this 就相当于操作 reactive,所以有响应性;同理 computed 也是一样。
缓存 computed
这里的computed有点小问题 —— 每次访问都会生成一个新的 computed,所以还要进行一下修改,可以加上缓存。
get getName() {
const re = toRaw(this).#cp['getName']
if (re){
return re
} else {
return toRaw(this).#cp['getName'] = computed(() => {
return this.name + '测试 computed'
})
}
}
这样调整之后,不会每次都创建一个新的 computed 了,只是每次还是需要判断一下。
看看结构
结构稍有不同,第一层是属性,getter和action在第二层,辅助函数依然在第三层。 这里的 getter 是一个函数,返回了一个computed。
源码
在线演示
转载自:https://juejin.cn/post/7236940669066330171