首先把大致的架子搭好。
let Vue;
let _self;
class Store {
constructor(state, getters, mutations, actions) {}
commit(cb, ...args) {}
dispatch(cb, ...args) {}
static install(_Vue, storeName = "store") {}
}
export default Store;
这块很简单,主要利用当调用Vue.use()
的时候,Vue
会自动调用传进参数的install
方法,如果传进来的是个函数则直接把这个函数当做install
方法。然后install
方法的第一个参数就是Vue构造函数
。通过这种方式就可以获取到Vue
。
static install(_Vue, storeName = "store") {
Vue = _Vue; // 保存备用
// 为了能够将Store实例挂载到Vue原型上,先将生成的Store实例挂载在根实例上
// 再通过全局混入得到根实例,则可拿到Store实例
Vue.mixin({
beforeCreate() {
const store = this.$options[storeName];
if (store) {
// 只在根实例上做一次赋值操作即可
Vue.prototype[`$${storeName}`] = store;
// 保存store备用
_self = store;
}
}
});
}
这块其实很简单,无非是要调用函数修改state
里面的值而已。
constructor({ state, getters, mutations, actions }) {
this.state = state || {};
this.mutations = mutations || {};
this.actions = actions || {};
this.getters = {}; // getters情况特殊一点,暂时这么写
}
commit(cb, ...args) {
// 由于mutations的声明过程中,形参可能是利用了解构的方式
// (this被解绑,在脚手架搭建的Vue项目中默认使用严格模式,
// 因此此时this指向undefined),因此为了保证this
// 始终指向store实例,利用install里面保存的_self
_self.mutations[cb].apply(_self, [_self.state, ...args]);
}
dispatch(cb, ...args) {
_self.actions[cb].apply(_self, [_self, ...args]);
}
在Vue
中,很重要的一个特征就是响应式。为了实现响应式,这里利用Vue.observable
。官网对Vue.observable
的解释如下:
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。
在 Vue 2.x 中,被传入的对象会直接被 Vue.observable 变更,所以它和被返回的对象是同一个对象。在 Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的。因此,为了向前兼容,我们推荐始终操作使用 Vue.observable 返回的对象,而不是传入源对象。
其实说到底就是这是Vue
内部用来实现响应式的一个工具函数,在2.6.0
版本的时候把它暴露出来给用户了。因此可以直接使用它达到响应式的效果。
代码如下,只需要给state
加上Vue.observable
即可
constructor({ state, getters, mutations, actions }) {
this.state = Vue.observable(state || {});
this.mutations = mutations || {};
this.actions = actions || {};
this.getters = {}; // getters情况特殊一点,暂时这么写
/**
* 注:也可以使用new Vue()实现响应式,
* 因为它内部也调用了 Vue.observable,如下:
* this.state = new Vue({ data: state || {} });
*/
}
这样一个简易的Vuex
就实现的差不多了,最后我们来实现getters
。
由于getters
是类似计算属性,因此要做一层代理。
constructor({ state, getters, mutations, actions }) {
this.state = Vue.observable(state || {});
this.mutations = mutations || {};
this.actions = actions || {};
this.getters = {}; // getters情况特殊一点,暂时这么写
const _getters = getters || {};
for (let fn in _getters) {
Object.defineProperty(this.getters, fn, {
// 注意这里this指向最后生成的对象,所以要使用_self
get() {
return _getters[fn].apply(_self.state, _self.getters);
}
});
}
/**
* 注:也可以使用new Vue()实现响应式,
* 因为它内部也调用了 Vue.observable,如下:
* this.state = new Vue({ data: state || {} });
*/
}
现在我们来测试一下。创建一个文件夹,里面放上我们的代码和index.js
文件。
index.js
代码如下:
import Vue from "vue";
import Store from "./Store";
Vue.use(Store, "myStore");
export default new Store({
state: {
num: 1,
num2: 1
},
getters: {
age(state, getters) {
return state.num + "岁, " + "性别:" + getters.gender;
},
gender(state) {
return state.num === 1 ? "男" : "女";
}
},
mutations: {
add(state) {
state.num++;
},
mul(state) {
state.num2 += 2;
}
},
actions: {
asyncAdd({ commit }) {
setTimeout(() => {
commit("mul");
}, 1000);
}
}
});
接着在main.js
中引入:
import Vue from "vue";
import App from "./App.vue";
import myStore from "./my-store";
new Vue({
myStore, // 别忘了注入
render: (h) => h(App),
}).$mount("#app")
最后在App.vue
中做测试:
<template>
<div id="app">
{{ $myStore.state.num }} <button @click="$myStore.commit('add')">Addbutton> <br />
{{ $myStore.state.num2 }} <button @click="$myStore.dispatch('asyncAdd')">asyncAddbutton> <br />
getters: {{ $myStore.getters.age }}
div>
template>
<script>
export default {
name: "App"
};
script>
let Vue;
let _self;
class Store {
constructor({ state, getters, mutations, actions }) {
this.state = Vue.observable(state || {});
this.getters = {};
this.mutations = mutations || {};
this.actions = actions || {};
const _getters = getters || {};
for (const fn in _getters) {
Object.defineProperty(this.getters, fn, {
get() {
return _getters[fn].apply(_self, [_self.state, _self.getters]);
}
});
}
}
commit(cb, ...args) {
_self.mutations[cb].apply(_self, [_self.state, ...args]);
}
dispatch(cb, ...args) {
_self.actions[cb].apply(_self, [_self, ...args]);
}
static install(_Vue, storeName = "store") {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
const store = this.$options[storeName];
if (store) {
// 只在根实例上做一次赋值操作即可
Vue.prototype[`$${storeName}`] = store;
// 保存store备用
_self = store;
}
},
});
}
}
export default Store;
以上。