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

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

上一篇我们实现了状态管理仓库的 getters, 但是实际应用中并不推荐我们直接更改store.state的值,所以我们需要实现一个actions来更新我们的状态

actions 的实现原理非常简单,只需要把action暴露出去给组件使用就行,并且actions支持同步和异步

接着上代码:

依然是创建一个store对象,store新增一个actions对象
该对象只需要在useStore的时候暴露给组件使用的store.state上就行
我们在执行useStore的时候将getters、store.actions 合并到store.state 就能实现
然后我们可以直接通过store.state[actionsName] 访问对应的actions

const store = {
  state: {
    val: 0
  },
  getters: {
  },
  actions: {
    add (value) {
      this.val += value
    },
    remove (value) {
      this.val -= value
    }
  },
  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
        }, {})
        // 直接将 this.actions 合并到 this.state 上就好了, action 函数里面可以直接通过 this 访问 state 对象
        Object.assign(this.state, getters, this.actions)
      }
    }
    const scope = Vue.effectScope()
    scope.run(setUp)
    return this.state
  }
}

然后我们创建两个子组件
不通过直接更改store.state的值去更新组件
而是通过store的 actions 去更新组件

<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>
    const store = {
      state: {
        val: 0
      },
      getters: {
      },
      actions: {
        add (value) {
          this.val += value
        },
        remove (value) {
          this.val -= value
        }
      },
      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
            }, {})
            // 直接将 this.actions 合并到 this.state 上就好了, action 函数里面可以直接通过 this 访问 state 对象
            Object.assign(this.state, getters, this.actions)
          }
        }
        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.add(1)
        }
        return {
          add,
          storeA
        }
      },
      template: `
        

children-one


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

children-two


val : {{storeB.val}}
`
}) const app = Vue.createApp({ name: 'app', components: { 'children-one': childrenOne, 'children-two': childrenTwo, }, template: `
`
}) app.mount('#app')
script> body>

我们来看下效果:

这样一个我们就实现了 actions

到这里一个基础的青铜版pinia基本就差不多实现了,基本实现原理就是这么多
要想达到和pinia一样的效果,比如多模块等其他优秀特性
我们还需要调整store的生成方式和数据组成等比较繁杂的内容,我们只是简单了解pinia实现原理
其他的就不深入研究了,有兴趣的小伙伴可以自己研究改造一下

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