本项目借助vue-cli快速搭建,package.json内容如下
{
"name": "myvuex2",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"less": "^4.0.0",
"less-loader": "^8.0.0",
"vue-template-compiler": "^2.6.14"
}
}
首先来看看vuex是怎么使用的呢?先引入Vuex,然后使用Vue.use(Vuex)注册组件,再实例化一个Store类。所以我们在手写vuex代码时需要暴露 install 函数(Vue.use(Vuex)会自动调用 Vuex 的 install 方法)和 Store类。
import Vue from 'vue'
import Vuex from '../vuex'
Vue.use(Vuex)
export default new Vuex.Store({
namespaced: true,
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
这个文件主要是整合内容并暴露给外界,暴露了 Store 类和 install 方法。
index.js
import { Store, install } from './store';
export default {
Store,
install
}
下面是vuex的核心部分,定义了 Store 类和 install方法。
import { forEach } from "./util";
可以看到在文件开头引入了 forEach,他是我们写的一个工具函数,作用是遍历一个对象中的所有的函数,并返回该函数的函数体和函数名。
export const forEach = (obj = {}, fu) => {
Object.keys(obj).forEach(key => fu(obj[key], key))
}
接下来就是Store类的构造函数中会分别调用工具函数 forEach 将传入的 getters、mutattions、actions 添加到对象上。同时定义 commit 函数,使实例可以调用 mutatiaons 中的方法,定义 dispatch 函数,使实例可以调用 actions 中的方法。
store.js
import { forEach } from "./util";
import applyMixin from "./mixin";
let Vue;
class Store{
constructor(options) {
// 保存getters
this.getters = {};
const computed = {};
// 遍历得到所有getters
forEach(options.getters, (fu, fuName) => {
// 增加缓存机制
computed[fuName] = () => {
return fu(this.state);
}
Object.defineProperty(this.getters, fuName, {
get: () => this._vm[fuName]
})
})
// 发布订阅者模式,将用户定义的mutation和actions先保存起来,当调用commit十九调用订阅的mutation,调用dipatch时就调用订阅action
this._mutations = {};
forEach(options.mutations, (fu, fuName) => {
this._mutations[fuName] = (payload) => fu.call(this, this.state, payload);
})
this._actions = {};
forEach(options.actions, (fu, fuName) => {
this._actions[fuName] = (payload) => fu.call(this, this, payload);
})
// 让数据变为响应式
this._vm = new Vue({
data: {//内部状态
$$state: options.state
},
computed //计算属性会将自己的属性放到实例上
})
}
// 可以使用本方法调用订阅的actions方法
dispatch = (fuName, payload) => {
this._actions[fuName](payload);
}
// 可以使用本方法调用订阅的mutation方法
commit = (fuName, payload) => {
this._mutations[fuName](payload);
}
// 类的属性访问器,当访问这个实例的state属性时会执行此方法
get state() {
return this._vm._data.$$state;
}
}
// 定义的install方法
const install = (_Vue) => {
Vue = _Vue;
applyMixin(Vue);
}
export {
Store,
install
}
在上面的文件中还定义了 install 方法,该方法在执行 Vue.use(Vuex) 时会自动调用,install 方法中主要是调用 mixin 方法,使每个组件都混入一个 $store 属性,他们都指向同一个 $store 实例。这样就可以达到数据共享的目的了。
mixin.js
const applyMixin = (Vue) => {
Vue.mixin({// 每个组件都会混入beforeCreate函数定义一个$store属性,指向同一个Sotre
beforeCreate() {
// 如果是根组件就为根组件添加$store属性
if (this.$options && this.$options.store) {
this.$store = this.$options.store;
} else {// 如果是子组件就将根组件的$store属性值赋值给当前组件的$store属性
this.$store = this.$parent && this.$parent.$store;
}
}
})
}
export default applyMixin;
vuex未实现命名空间的原理还是很简单,很好理解的,要点在于对 Vue.mixin 的方法的应用,该方法可以让所有组件共享一个 $store 实例,实现数据共享。另一要点就是对 getters、mutattions、actions 的搜集,以及state的响应式和 getter 的缓存机制十分巧妙。最后的难点是 vuex 的模块与命名空间的实现,它的嵌套模块递归处理很复杂,当然本项目里边并未涉及,感兴趣的小伙伴可以自己去看一看。