likes
comments
collection
share

基于Vue3做一套适合自己的状态管理(二)继承:充血实体类

作者站长头像
站长
· 阅读数 68

计划章节

  1. 基类:实现辅助功能
  2. 继承:充血实体类
  3. 继承:Option 风格的状态
  4. Model:正确的打开方式
  5. 组合:setup 风格,更灵活
  6. 注册状态的方式和状态的有效范围
  7. 列表页面需要的状态
  8. 当前登录用户的状态

继承:使用充血实体类的方式实现状态

上一篇实现了一个基类(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

看看结构

基于Vue3做一套适合自己的状态管理(二)继承:充血实体类

层次非常分明,第一层是属性和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 了,只是每次还是需要判断一下。

看看结构

基于Vue3做一套适合自己的状态管理(二)继承:充血实体类

结构稍有不同,第一层是属性,getter和action在第二层,辅助函数依然在第三层。 这里的 getter 是一个函数,返回了一个computed。

源码

gitee.com/naturefw-co…

在线演示

naturefw-code.gitee.io/nf-rollup-s…

转载自:https://juejin.cn/post/7236940669066330171
评论
请登录