vue3: 2.如何利用 effectScope 自己实现一个青铜版pinia 一 getters篇

vue3: 如何利用 effectScope 自己实现一个青铜版pinia - getters篇

上一篇 我们实现了状态管理仓库的 state, pinia和vuex中都有计算属性getters, 接下来我们就来实现计算对象 getters

我们都知道getters具有计算属性,依赖于其他state和 getter 值进行计算新的值
如何在store中实现这个效果,vue3中有一个cumputed api可以让我们使用

上代码:
依然是创建一个store对象,store新增一个getters对象
我们写getters的时候是一个函数,函数的返回值才是最终使用的数据
那么我们可以在useStore 第一次执行的时候利用 computed 将getters 里面的所有函数执行并返回一个新的getters对象,该对象也是具有响应式的
得到新的getters对象后我们将getters对象合并到store.state 对象上
组件中使用getters上的属性时可以直接通过 store.state[gettersNmae] 访问

const store = {
  state: {
    val: 0
  },
  getters: {
    computedVal (state) {
      return state.val + 1
      // 或者直接通过this访问
      // 为何可以通过this访问,请查看getters合并到state的操作
      // 原理就是利用call函数将this对象指向了store.state 对象
      // return this.val + 1
    },
    computedVal2 (state) {
      // 直接访问 计算属性 computedVal
      return this.computedVal + 1
    }
  },
  useStore () {
    const setUp = () => {
      if (!Vue.isReactive(this.state)) {
        // 以下的操作只需要执行一次
        // 因为执行setup做响应式依赖收集的时候会在每个组件执行,访问的是同一个state,该state 对象的代理实现只需要第一次就够了
        this.state = Vue.reactive(this.state)
        const _this = this
        // 将this.getters里面的函数取出,存到 getters 这个对象里面的值变为具有computed 计算属性的一个对象
        const getters = Object.keys(_this.getters || {}).reduce(function (conmputedGetters, name) {
          // 利用computed 计算this.getters里面的值, 并记录下来
          conmputedGetters[name] = Vue.computed(() => {
            return _this.getters[name].call(_this.state, _this.state)
          })
          return conmputedGetters
        }, {})
        // 合并 getters对象 到 this.state
        Object.assign(this.state, getters)
      }
    }
    const scope = Vue.effectScope()
    scope.run(setUp)
    return this.state
  }
}

然后我们创建两个子组件并使用这个store的getters

<body>
  <script
    src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.37/vue.global.min.js"
    integrity="sha512-rCO3ZZnxh9j/Y725Iq2Cqr2lc9fi83zVeN3PFTUosktylZsCFjD13PDbKrzKjKO/idjM4KlMQC52AsoGFTAe6A=="
    crossorigin="anonymous"
    referrerpolicy="no-referrer"
  >script>
  <div id="app">div>
  <script>
    // 实现一个getters
    const store = {
      state: {
        val: 0
      },
      getters: {
        computedVal (state) {
          return state.val + 1
          // 或者直接通过this访问
          // 为何可以通过this访问,请查看getters合并到state的操作
          // 原理就是利用call函数将this对象指向了store.state 对象
          // return this.val + 1
        },
        computedVal2 (state) {
          // 直接访问 计算属性 computedVal
          return this.computedVal + 1
        }
      },
      useStore () {
        const setUp = () => {
          if (!Vue.isReactive(this.state)) {
            // 以下的操作只需要执行一次
            // 因为执行setup做响应式依赖收集的时候会在每个组件执行,访问的是同一个state,该state 对象的代理实现只需要第一次就够了
            this.state = Vue.reactive(this.state)
            const _this = this
            // 将this.getters里面的函数取出,存到 getters 这个对象里面的值变为具有computed 计算属性的一个对象
            const getters = Object.keys(_this.getters || {}).reduce(function (conmputedGetters, name) {
              // 利用computed 计算this.getters里面的值, 并记录下来
              conmputedGetters[name] = Vue.computed(() => {
                return _this.getters[name].call(_this.state, _this.state)
              })
              return conmputedGetters
            }, {})
            // 合并 getters对象 到 this.state
            Object.assign(this.state, getters)
          }
        }
        const scope = Vue.effectScope()
        scope.run(() => {
          // 执行setup函数,收集响应式依赖
          return setUp()
        })
        return this.state
      }
    }
    // 子组件1
    const childrenOne = Vue.defineComponent({
      name: 'children-one',
      setup () {
        // 为什么要执行useStore,因为要通过 useStore 内的setup 函数为该组件提供响应式的依赖搜集
        // storeA 已经是一个包含getters的最基本的状态管理对象了
        // 不过我们一般不建议直接修改store的值,后续会提供actions去帮助大家更改state的值
        const storeA = store.useStore()
        const add = () => {
          storeA.val += 1
        }
        return {
          add,
          storeA
        }
      },
      template: `
        

children-one


计算属性 computedVal: {{storeA.computedVal}}
计算属性 computedVa2l: {{storeA.computedVal2}}
`
}) // 子组件2 const childrenTwo = Vue.defineComponent({ name: 'children-two', setup () { const storeB = store.useStore() const remove = () => { storeB.val -= 1 } return { remove, storeB } }, template: `

children-two


计算属性 computedVal: {{storeB.computedVal}}
计算属性 computedVa2l: {{storeB.computedVal2}}
`
}) const app = Vue.createApp({ name: 'app', components: { 'children-one': childrenOne, 'children-two': childrenTwo, }, template: `
`
}) app.mount('#app')
script> body>

我们来看下效果:

这样一个我们就实现计算属性 getters

但是实际应用中并不推荐我们直接更改store.state的值
包括vuex中在mutions之外更改state的值也会警告
况且pinia中组件里面gettes属性的读取都是通过store.state对象直接读取的
在开发过程中 store.state.val = xxx 这样更改状态是非常不可控的
如果getters的computedVal值被这样赋值 store.state.computedVal = xxx,他将会失去计算属性和响应式

所以我们还需要一个能够修改状态的actions,这就是我在下一篇讲要讲的内容了

你可能感兴趣的:(vue,pinia,vue.js,前端,javascript)