vuex-class是在class-component中使用vuex的辅助工具。
学习任何技术栈的使用,最透彻的掌握方法就是去简单实现一下,下面先简单实现一下vuex,然后基于我们自己实现的vuex再去实现一个vuex-class,彻底搞定vuex-class的使用。
首先回忆一下vuex的使用(配置)方法,首先我们需要在某个位置执行Vue.use(Vuex)
,然后通过new Vuex.Store
的方式创建一个Store实例,在实例化Vue时将其传入配置对象:new Vue({store})
。
src/store/index.js
:
import Vue from 'vue';
import Vuex from 'vuex';
?
Vue.use(Vuex);
?
export default new Vuex.Store({
? ?state: {
? ? ? ?count: 0
? }
})
main.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对象的属性,使用state
、commit
…
为了保留核心,删繁就简,我们自己的vuex只实现在组件中通过this.$store.state
访问响应式state的能力。
因为vue2中的组件其实就是一个对象,整个项目以App.vue
作为根组件,与所有子组件构成了一个巨大的组件树,说白了就是一个对象树,并且基于当前组件对象可以通过$parent
等属性访问组件树的相邻(父)组件。
那么我们既然要让所有组件都可以通过$store
属性访问到store对象,那么就是很单纯的给所有组件都加一个$store
属性就好了。
借助Vue的插件机制:Vue.use
方法可以接收一个对象,并执行这个对象的install
方法,Vue
构造函数将作为install
的参数,我们可以在install
的逻辑中通过Vue.mixin
给未来要实例化的所有组件注入逻辑!
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):
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构造函数能根据option.state
创建一个响应式的state对象呢?vue2并没有像vue3中提供与组件解绑的方法(如ref
、reactive
)来创建响应式数据,所以最朴素也是唯一的一个方法就是创建一个vue组件实例,并利用它的data
配置来获取一个响应式数据!
install
方法的执行早于Store
实例的创建,所以在install
中对Vue
进行引用记录,让Store
的构造函数中可以使用它来创建组件。
+ 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
的导入地址为:
import Vuex from './myVuex';
测试组件:
// 父组件
<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发生变化。
同样,为了理清核心逻辑,我们只实现一下State
方法,先回顾一下store中的数据如何在class-component中使用的。
import { State } from 'vuex-class';
?
export default class Xxx extends Vue {
?@State((state) => state.xxx) xxxS!: any;
?
?someFun() {
? ?// this.xxxS
}
}
其实概括来讲就是利用装饰器在执行class代码进行组件初始化的同时,对options组件对象进行同步构建。并且在文章中详解了vue-property-decorator
库的@Ref
方法的实现,概括来说就是将class属性映射为options组件的计算属性,实现class组件代码中this.xxxRef
到this.$refs.xxxRef
的映射。
其实明白了上面的思路,这里要实现State
方法就手到擒来了,与@Ref
的实现可谓“换汤不换药”。
src/store/myVuexClass.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
的构建了,这里不再赘述。
完整附件:点此下载