Vuex
Vuex 是一个专为Vue.js开发的状态管理插件。state定义了数据源,mutation,action等实现了对数据的管理。
Vuex插件引入以及store的注入
在vue项目内通过 Vue.use(Vuex)引入。Vue.use()调用Vuex的install方法。
install 在文件 src\store.js
export function install (_Vue) {
Vue = _Vue
applyMixin(Vue)
}
applyMixin 在文件 src\mixin.js
export default function (Vue) {
// 全局混入,在每个组件的beforeCreate生命周期执行vuexInit
Vue.mixin({ beforeCreate: vuexInit })
function vuexInit () {
const options = this.$options
if (options.store) {
// 根组件注入store
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 子组件注入store
// 根据生命周期父组件的beforeCreate在子组件之前执行,保证了子组件也可以注入store
this.$store = options.parent.$store
}
}
}
Store初始化
class Store {
constructor(options = {}) {
this._committing = false // 判断是否通过commit修改state
this._actions = Object.create(null) // actions的类型和对应的函数
this._actionSubscribers = [] // dispatch时要触发的函数
this._mutations = Object.create(null) // mutations的类型和对应的函数
this._wrappedGetters = Object.create(null) // getters的类型和对应的函数
this._modules = new ModuleCollection(options) // module数据结构转换
this._modulesNamespaceMap = Object.create(null) // module名字和对应的module
this._subscribers = [] // commit时要触发的函数
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
const state = this._modules.root.state
// 注册module
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)
plugins.forEach(plugin => plugin(this))
}
}
installModule:
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
// 获取命名空间(namespace)
const namespace = store._modules.getNamespace(path)
// 注册module
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
// 注册muatation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注册getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 遍历子module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
resetStoreVM内将数据变成响应式:
function resetStoreVM (store, state, hot) {
store.getters = {}
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
// 将getter转换成computed格式,并代理到store
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true
})
})
// 将state变成响应式
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
代理,使this.$store.state获取的是store._vm._data.$$state
get state () {
return this._vm._data.$$state
}
注册监听(subscribe,subscribeAction)
subscribe在src\store.js
subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
}
subscribeAction在src\store.js
subscribeAction (fn, options) {
// 如果是函数则添加增加before属性,也可以传入{ after: fn },{ error: fn }
// 当触发action时,会根据before,after,error属性在不同的时机执行fn
const subs = typeof fn === 'function' ? { before: fn } : fn
return genericSubscribe(subs, this._actionSubscribers, options)
}
subscribe,subscribeAction都会去调用genericSubscribe去注册监听。
subscribe注册的监听只会在触发commit时执行。
subscribeAction注册的监听只会在触发dispatch时执行。
genericSubscribe在src\store.js
function genericSubscribe (fn, subs, options) {
// 判断监听列表里是否已经有该函数,没有则添加
// 按照配置项将函数添加至首部还是尾部
if (subs.indexOf(fn) < 0) {
options && options.prepend
? subs.unshift(fn)
: subs.push(fn)
}
// 返回取消监听的方法
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
触发action(dispatch)
dispatch(type, payload) {
const action = { type, payload }
// 获取对应action类型的函数
const entry = this._actions[type]
// 将准备在action触发前的函数全部执行
// 异步,不会等待before,after,error执行完之后才执行后续的步骤
this._actionSubscribers.slice().filter(sub => sub.before).forEach(sub => sub.before(action, this.state))
// 执行对应action类的函数
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
// 将准备在action触发后的函数全部执行
this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))
resolve(res)
}, error => {
// 将准备在action异常时的函数全部执行
this._actionSubscribers.filter(sub => sub.error).forEach(sub => sub.error(action, this.state, error))
reject(error)
})
})
}
触发mutation(commit)
commit(type, payload, options) {
const mutation = { type, payload }
// 获取对应mutations类型的函数
const entry = this._mutations[type]
// 执行真正对应mutations类型的函数
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 执行subscribe注册的函数
this._subscribers.slice().forEach(sub => sub(mutation, this.state))
}
真正的函数在_withCommit函数内执行。
_withCommit在src\store.js
_withCommit (fn) {
// 因为vuex规定通过commit来修改store,但是用户也可以直接修改store
// 所以此处通过commiting用于判断用户用哪种方式进行修改
// vuex监听了store的状态变化,如果在严格模式的开发模式下直接修改store,控制台会提示;该功能通过enableStrictMode实现
const committing = this._committing
this._committing = true
fn()
this._committing = committing
enableStrictMode在src\store.js
function enableStrictMode (store) {
// 通过vue的watch监听state
store._vm.$watch(function () { return this._data.$$state }, () => {
if (__DEV__) {
// 在开发模式下,如果store的值更改了,且_committing为true,就会在控制台进行提示
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}
辅助函数(mapState,mapMutations,mapActions,mapGetters)
mapState:将对应state内的属性返回,和vue内的computed合并
mapGetters:将对应的getter返回,和vue内的computed合并
mapActions:将对应的action返回,和vue内的method合并
mapMutation:将对应的mutation返回,和vue内的method合并
以mapState为例子,其他同理:
// 举个栗子
// 多模块的两种取值写法
// computed: {
// ...mapState({
// list: state => state.cart.list
// }),
// // ...mapState('cart', ['list'])
// },
const mapState = (namespace, map) => {
// 第一种写法:
// namesapce:{list: state => state.cart.list}
// map:undefined
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
// map:{list: state => state.cart.list}
return (namespace, states) => {
const res = {}
// normalizeMap(states)=>[{key:'list'},val:state => state.cart.list]
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
// 获取state和getter
let state = this.$store.state
let getters = this.$store.getters
// 如果有命名空间,就去对应的命名文件内找state和getter
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
// 根据写法获取对应的value值
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
res[key].vuex = true
})
return res
}
}
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
}
// 根据写法获取对应的key值
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
总结
- vuex流程:
定义state,通过dispatch(有异步逻辑情况下使用,会调用action)或者commit(会调用mutation)方法去修改值。也可以通过mapActions或者mapMutation获取对应的action和commit方法,再去修改。
getter相当于state的计算属性。
- vuex可以直接修改state的值,且触发响应式;但是规范要求最好不要直接修改,而且在严格模式下控制台会报错。
- vuex可以在mutation内执行异步操作,但是规范要求在action内执行异步操作;因为会导致数据混乱,难以调试。
- vuex响应式原理:借用vue的响应式原理,将state绑定到data上,getter绑定到computed上。
- 各组件都可以访问到store的原因:在插件被调用时会将beforeCreate混入到各个组件,在执行beforeCreate时会将store绑定到各个组件上。
- mapActions,mapMutation,mapGetters,mapState本质上就是获取对应的属性,再和组件内的method,computed对象合并。