vuex是vue的状态管理工具,便于状态共享和传递
vuex的使用
创建store
- Vue.use使用Vuex插件,默认调用Vuex的install方法
- 创建store实例,传入参数options
- state:单一的数据源,响应式
- getters:计算属性
- mutations:同步改变state
- actions:异步改变state
// store.js
import Vuex from './vuex'
Vue.use(Vuex)
let store = new Vuex.Store({
state: { age: 10 },
getters: {
myAge(state) {
return state.age + 20
}
},
mutations: {
syncChange(state, payload) {
setTimeout(() => {
state.age += payload
}, 1000)
}
},
actions: {
asyncChange({commit}, payload) {
setTimeout(() => {
commit('syncChange', payload)
}, 1000)
}
}
})
注入:在main.js中注入store
new Vue({
name: "root",
store,
render: h => h(App)
}).$mount('#app')
所有组件添加$store属性原理
- 当执行Vue.use时,默认执行插件的install方法
- install方法:借助Vue.mixin和beforeCreate为所有组件提供$store属性获取store
const install = (_Vue) =>{//传递_Vue的目的:在写vue插件的时候,无需安装新的vue,避免版本不同出现问题
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.store) { // 说明是根实例
this.$store = this.$options.store
} else { // 组件的创建过程是先父后子,通过将$store自上而下赋值,实现所有子组件都有$store
this.$store = this.$parent && this.$parent.$store
}
},
});
}
Store实现
- 实现state响应式:通过new Vue创建vue实例,对store中的数据进行劫持和依赖收集;通过get方法返回实例上的state
- getters原理:在store实例上定义属性getters;遍历用户定义的getters对象,通过Object.defineProperty进行劫持,传入store上的state
- mutations同步更新状态(发布订阅+函数劫持):订阅---在store实例上定义属性mutations为一个对象; 遍历用户定义的mutations,进行函数劫持,传入store.state和其他参数;发布---当执行commit方法时执行指定的方法
- actions异步更新状态:原理同上,当执行dispatch时执行
let forEach = (obj, callback) => {
Object.keys(obj).forEach(key => {
callback(key, obj[key])
})
};
class Store{
constructor(options) {
let {state, getters, mutations, actions} = options;
// new Vue实现state的响应式
this.vm = new Vue({
data: { state}
});
// getters通过Object.defineProperty进行劫持
this.getters = {}
forEach(getters, (getterName, value) => {
Object.defineProperty(this.getters,getterName, {
get: () => {
return value(this.state)
}
})
})
// mutations:发布订阅+函数劫持
this.mutations = {}
forEach(mutations, (mutationName, value) => {
this.mutations[mutationName] = (payload) => {
value(this.state, payload)
}
})
// actions:发布订阅+函数劫持
this.actions = {}
forEach(actions, (actionName, value) => {
this.actions[actionName] = (payload) => {
value(this, payload);
}
})
}
dispatch = (actionName, payload) => {
this.actions[actionName](payload)
}
commit = (mutationName, payload) => { // 2.3.2 触发mutation--发布
this.mutations[mutationName](payload)
}
// 返回state
get state() {
return this.vm.state
}
}
模块的收集和安装
vuex中modules,用于定义指定模块的属性和方法
模块收集
- 将模块上的数据格式化
- 顶层的模块内容保存的this.root上;其他保存在父级的_children上
// 格式化结果:
let root = {
_raw: rootModule,
state: rootModule.state,
_children: {
a: {
_raw: aModule,
state: aModule.state,
_children: {}
},
}
}
// 实现
class ModuleCollection{
constructor(options) {
this.options = options
this.register([], options)
}
// 例如:options: {modules: {a: {modules: { a1: {}, a2: {} }}, b: {}}}
register = (path, rootModule) => {
let rawModule = { // 格式化后的模块内容
_raw: rootModule,
state: rootModule.state,
namespace: rootModule.namespace,
_children: {}
}
rootModule.rawModule = rawModule; // 双向记录
if (!this.root) {
this.root = rawModule
} else { // 通过reduce定位父级模块
// 如果是第二层:[a],parentModule是this.root
// 如果是第三层: [a, a1],parentModule是this.root._children.a对应的对象
let parentModule = path.slice(0, -1).reduce((root, current) => {
return root._children[current]
}, this.root)
parentModule._children[path[path.length - 1]] = rawModule
}
if (rootModule.modules) {
// 如果是第二层:modules: {a: {modules: { a1: {}, a2: {} }}, b: {}}
// 如果是第三层:modules: { a1: {}, a2: {} }
forEach(rootModule.modules, (moduleName, module) => {
// 如果是第二层:path--module: [a]-- {modules: { a1: {}, a2: {} }}
// 如果是第三层:path--module: [a, a1]--{}
this.register(path.concat(moduleName), module)
})
}
}
}
模块安装-state、getters、mutations、actions
- 模块安装,其实是将所有模块中的state、getters、mutations、actions等属性和方法绑定在store上,用于改变状态和视图刷新
- namespace:用于调用指定空间的属性和方法
- 安装state:根据path找到父级state,通过Vue.set在父级state上设置{key为模块名:value为当前模块的state}实现数据的响应式
- 安装getters:注意避免重名
- 安装mutations:根据mutationName进行数组存放
- 安装actions:根据actionName进行数组存放
- getState:返回store上对应path的state
function installModule(store, rootState, path, rawModule) {
// 如果有命名空间,定义命名空间前缀
let root = store.modules.root;
let namespace = path.reduce((str, current) => {
root = root._children[current];
return str = str + (root._raw.namespace ? current +
'/' : '')
}, '');
// 安装state
if (path.length > 0) {
let parentState = path.slice(0, -1).reduce((root, current) => {
return rootState[current]
}, rootState)
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
//安装getters:避免重名
let getters = rawModule._raw.getters;
if (getters) {
forEach(getters, (getterName, value) => {
if (!store.getters[namespace+getterName]) {
Object.defineProperty(store.getters, namespace+getterName, {
get: () => {
return value(getState(store, path))
}
})
}
})
}
// 安装mutations:同名的mutation放在一个数组中,挂载store上
let mutations = rawModule._raw.mutations;
if (mutations) {
forEach(mutations, (mutationName, value) => {
let arr = store.mutations[namespace+mutationName] || (store.mutations[namespace+mutationName] = [])
arr.push((payload) => {
value(getState(store, path), payload)
})
})
}
// 安装actions:同名的action放在一个数组中,挂载store上
let actions = rawModule._raw.actions;
if (actions) {
forEach(actions, (actionName, value) => {
let arr = store.actions[namespace+actionName] || (store.actions[namespace+actionName] = [])
arr.push((payload) => {
value(store, payload)
})
})
}
// 递归安装
if (rawModule._children) {
forEach(rawModule._children, (moduleName, rawModule) => {
installModule(store, rootState, path.concat(moduleName), rawModule)
})
}
}
function getState(store, path) {
let local = path.reduce((newState, cur) => {
return newState[cur]
}, store.state)
return local
}
插件的执行原理-发布订阅
- 在创建store实例时,循环插件执行,传递store实现插件的初始化
- 插件初始化时,会执行store的subscribe方法,它将订阅插件的具体执行逻辑
- 当触发mutation改变state时,会触发插件的执行逻辑;所以,在安装模块中的mutation时,进行发布
function logger(store) { // logger插件
console.log(store, 'store');
let preState = JSON.stringify(store.state);
store.subscribe((mutation, newState) => {
console.log(preState);
console.log(mutation);
console.log(JSON.stringify(newState));
preState = JSON.stringify(newState)
})
}
---------------------------------------
class Store{
constructor(options) {
...
this.subs = [];
let {state, plugins, strict} = options;
plugins.forEach(plugin => plugin(this)) // 插件初始化
...
}
subscribe = (fn) => { // 插件订阅
this.subs.push(fn)
}
...
}
function installModule(store, rootState, path, rawModule) {
...
let mutations = rawModule._raw.mutations;
if (mutations) {
forEach(mutations, (mutationName, value) => {
let arr = store.mutations[namespace+mutationName] || (store.mutations[namespace+mutationName] = [])
arr.push((payload) => {
value(getState(store, path), payload);
// 插件发布
store.subs.forEach(sub => sub({type: namespace+mutationName, payload:payload}, store.state))
})
})
}
...
}
插件数据持久化persist & store.replaceState
- replaceState:新数据替换store中的老数据
// persist
function persist(store) {
let local = localStorage.getItem('VUEX:state');
if(local) {
store.replaceState(JSON.parse(local))
}
store.subscribe((mutation, newState) => {
localStorage.setItem('VUEX:state', JSON.stringify(store.state))
})
}
class Store{
...
replaceState = (newState) => {
this.vm.state = newState
}
...
}
mutation的异步监听
- 重写commit为_withCommit(函数劫持):定义变量_committing,用于区分mutation是否为异步;所有改变状态的函数都需要_withCommit进行劫持
- 严格模式下,通过vm.$watch监听store中的state;
- 当mutation为异步时,this._committing为false,导致console.assert在控制台输出
class Store{
constructor(options) {
...
this._committing = false
if (this.strict) {
this.vm.$watch(() => {
return this.vm.state
}, function() {
console.assert(this._committing,'不能异步调用')
}, {deep: true, sync: true})
}
...
}
···
_withCommit = (fn) => {
const committing = this._committing;
this._committing = true;
fn();
this._committing = committing // 如果是同步,this._committing为true,如果是异步,则为false
}
commit = (mutationName, payload) => { // 重写commit
this._withCommit(() => {
this.mutations[mutationName].forEach(fn => fn(payload))
})
}
replaceState = (newState) => {
this._withCommit(() => {
this.vm.state = newState
})
}
}