vuex与vuex-class底层学习 & 简版实现

vuex-class是在class-component中使用vuex的辅助工具,如果不清楚class-component是什么以及为什么要用vue-class-component,可以我上篇文章

学习任何技术栈的使用,最透彻的掌握方法就是去简单实现一下,下面先简单实现一下vuex,然后基于我们自己实现的vuex再去实现一个vuex-class,彻底搞定vuex-class的使用。

首先回忆一下vuex的使用(配置)方法,首先我们需要在某个位置执行Vue.use(Vuex),然后通过new Vuex.Store的方式创建一个Store实例,在实例化Vue时将其传入配置对象:new Vue({store})

src/store/index.js

js 复制代码
import Vue from 'vue';
import Vuex from 'vuex';
​
Vue.use(Vuex);
​
export default new Vuex.Store({
    state: {
        count: 0
    }
})

main.js

js 复制代码
import Vue from 'vue'
import App from './App.vue'
import store from './store'
​
Vue.config.productionTip = false
​
new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

经过如上的配置,我们就可以在组件中通过this.$store.xxx去访问Store对象的属性,使用statecommit...

为了保留核心,删繁就简,我们自己的vuex只实现在组件中通过this.$store.state访问响应式state的能力。

实现vuex

思路分析

因为vue2中的组件其实就是一个对象,整个项目以App.vue作为根组件,与所有子组件构成了一个巨大的组件树,说白了就是一个对象树,并且基于当前组件对象可以通过$parent等属性访问组件树的相邻(父)组件。

那么我们既然要让所有组件都可以通过$store属性访问到store对象,那么就是很单纯的给所有组件都加一个$store属性就好了。

实现切入点

借助Vue的插件机制:Vue.use方法可以接收一个对象,并执行这个对象的install方法,Vue构造函数将作为install的参数,我们可以在install的逻辑中通过Vue.mixin给未来要实例化的所有组件注入逻辑!

代码实现

整个组件树挂载$store

Vuex对象中除了install方法之外,还要有一个Store构造函数(new Vuex.Store(...)),我们暂且不管这个构造函数究竟如何创建实例store,我们先让所有组件都能拿到它创建的实例store

因为在main.js中我们把store实例传给了new Vue的配置对象,也就是说可以通过根组件对象的$options.store拿到store

对于App.vue这个根组件,为了达成通过this.$store访问store对象的目的,我们完全可以在其beforeCreate生命周期中执行this.$store = this.$options.store,但是对于组件树上的任意组件我们无法访问到根组件,但熟悉vue渲染机制的话我们知道:组件的渲染是从外至内的,也就是先创建父组件再是其子组件 ,所以借助整个App渲染时从外至内的"遍历"顺序,我们可以轻松写出如下算法,即除了根组件之外,让所有组件都去$parent上去找store对象并给自己的$store属性赋值,因为当前组件渲染时可以确定其父组件已经完成渲染:

src/store/myVuex.js(未来替换vuex):

js 复制代码
class Store {
    constructor(options) {
    }
}
​
const install = function(Vue) {
    Vue.mixin({
        beforeCreate(){
            if (this.$options && this.$options.store){ // 如果是根组件($options.store非空)
                this.$store = this.$options.store
            }else { //如果是任意子组件
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}
​
const Vuex = {
    Store,
    install,
}
​
export default Vuex;

store数据响应式处理

现在的问题在于如何让我们Store构造函数能根据option.state创建一个响应式的state对象呢?vue2并没有像vue3中提供与组件解绑的方法(如refreactive)来创建响应式数据,所以最朴素也是唯一的一个方法就是创建一个vue组件实例,并利用它的data配置来获取一个响应式数据!

install方法的执行早于Store实例的创建,所以在install中对Vue进行引用记录,让Store的构造函数中可以使用它来创建组件。

js 复制代码
+ let _Vue;
  class Store {
    constructor(options) {
+       this.vm = new _Vue({
+           data: {
+               state: options.state
+           }
+       })
    }
+   get state() {
+       return this.vm.state;
+   }
  }
​
const install = function(Vue) {
+   _Vue = Vue;
    Vue.mixin({
        beforeCreate(){
            if (this.$options && this.$options.store){
                this.$store = this.$options.store
            }else {
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}
​
const Vuex = {
    Store,
    install,
}
​
export default Vuex;

这样我们的vuex就基本实现了,替换vuex的导入地址为:

js 复制代码
import Vuex from './myVuex';

测试组件:

html 复制代码
// 父组件
<template>
  <div id="app">
    vuex中的数据:count--{{ this.$store.state.count }}
    <ChildComponent />
    <ClassChildComponent />
  </div>
</template>
​
<script>
import ChildComponent from './components/ChildComponent.vue'
​
export default {
  name: 'App',
  components: {
    ChildComponent,
  },
  mounted() {
    console.log(this);
  }
}
</script>
​
// 子组件
<template>
  <div>
    <hr />
    child
    <button @click="addCount">addCount</button>
  </div>
</template>
​
<script>
export default {
  name: 'ChildComponent',
  methods: {
    addCount() {
      this.$store.state.count += 1;
    }
  }
}
</script>

子组件中点击按钮父组件ui发生变化。

实现vuex-class

(建议先读vue2技术栈思考 & vue-class-component原理分析

同样,为了理清核心逻辑,我们只实现一下State方法,先回顾一下store中的数据如何在class-component中使用的。

js 复制代码
import { State } from 'vuex-class';
​
export default class Xxx extends Vue {
  @State((state) => state.xxx) xxxS!: any;
  
  someFun() {
    // this.xxxS
  }
}

这篇文章详细分析了class-componet的运行原理,其实概括来讲就是利用装饰器在执行class代码进行组件初始化的同时,对options组件对象进行同步构建。并且在文章中详解了vue-property-decorator库的@Ref方法的实现,概括来说就是将class属性映射为options组件的计算属性,实现class组件代码中this.xxxRefthis.$refs.xxxRef的映射。

其实明白了上面的思路,这里要实现State方法就手到擒来了,与@Ref的实现可谓"换汤不换药"。

src/store/myVuexClass.js

js 复制代码
import { createDecorator } from "vue-class-component";
​
export function State(selector) {
    return createDecorator((options, key) => {
        options.computed = options.computed || {};
        options.computed[key] = {
            cache: false,
            get() {
              return selector(this.$store.state);
            },
          }
    });
}

我们丢给State方法一个selector函数参数,最终构造的计算属性中调用selector时把this.$store.state扔进去让使用者去选所需的state中的状态就好了。

总结来说,如果要实现vuex-class,本质还是对于options组件对象的构建,实现State方法,是对其计算属性的构建,如果要实现mutation或者action,那么就是对option组件对象的methods的构建了,这里不再赘述。

相关推荐
崔庆才丨静觅24 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax