vuex
基本概念
Vuex状态管理
状态管理模式
- state,驱动应用的数据源
- view,以声明方式将state映射到视图
- actions,响应在view上的用户输入导致的状态变化
应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多少个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
Vuex
- 一个全局的单例模式
- 核心就是store,包含着应用中大部分的状态。
Vuex和单纯的全局对象的不同:
- Vuex的状态存储是响应式的。Store中的状态发生变化时,相应的组件也会相应地得到高效更新。
- 不能直接修改store中的状态。改变store中状态的唯一方式就是显示提交(commit)mutation。
Vuex是一个不闭合的状态管理模式实例,它将状态管理模式中的数据state和变更state的事物actions、mutations与Vue组件进行结合,形成一个闭合的状态管理。组件通过显示提交修改Vuex中数据,Vuex中数据结合Vue响应式原理来触发组件更新。其中,Devtools工具可以监听Vuex中mutations事务中对state的修改。
Store
Store构造器选项
state – 存储数据,Store实例的根state对象
1
2
3state: function () {
return {}
}传入的是一个函数,是为了防止state被共享,创建单例想法。与vue中data是一个函数想法一致。
getters – store实例的计算属性,类似于computed
1
2
3
4
5getters: {
getShoppingCartLen(state) {
return state.shoppingCart.length
}
}
mutations – 修改store中状态的处理事务,类似事件注册
1
2
3
4
5
6
7// 注册事务
import {SET_USER_NAME} from 'mutations-types'
mutations: {
SET_USER_NAME: function (state, payload) {
state.user.name = payload
}
}1
2// 提交修改store中状态
this.$store.commit('SET_USER_NAME', 'zhaoshijuan')vuex的store是响应式的,因此需要mutation操作store中状态时注意以下几点:
- 提前在store中初始化好所有所需属性
- 需要添加新属性:Vue.set(obj, property, value); 或者 新对象替换老对象。
actions – 异步操作事务
1
2
3
4
5actions: {
setusername: function (context, payload) {
context.commit('SET_USER_NAME', payload)
}
}actions与mutations区别:action提交的是mutation,mutation直接修改状态;action可以包含异步操作。
modules – 模块
1
2
3
4
5
6
7
8
9modules: {
user: {
namespaced: true,
state: {name: ''},
getters: {},
mutations: {},
actions: {}
}
}所有有关状态的,拿到的都是局部状态,包含context.state指向的也是局部状态。context.rootState指向根状态,或者作为参数rootState传递。
plugins – 插件,方法数组,store是方法参数
strict – 是否是严格模式。严格模式下,任何mutation处理函数以外修改Vuex state都会报错。
devtools – 订阅到devtools插件
Store实例属性
- state – this.$store.state是store中状态
- getters – this.$store.getters是store中getters
Store实例方法
commit – 提交mutation
1
2
3
4
5this.$store.commit('mutationType', mutationPayload, options);
this.$store.commit({
type: 'mutationType',
mutationPayload: mutationPayload
}, options)dispatch – 分发action
1
2
3
4
5this.$store.dispatch(type, payload, options);
this.$store.dispathc({
type: type,
payload: payload
}, options)replaceState – 替换store的根状态
- watch – 响应式监听fn返回值。watch(fn, callback, options)
- subscribe – 订阅mutation,mutation完成后调用
- subscribeAction – 订阅action,默认是在action调用之前触发。含有before,after指定订阅处理函数在action分发之前,之后调用
- registerModule – 注册指定path的module
- unregisterModule – 卸载指定path的module
- hotUpdate – 热替换新的action和mutation
辅助函数
- mapState – 在计算属性中混入store中状态
- mapGetters – 计算属性中混入store中getters
- mapMutations – 方法methods混入mutation事务
- mapActions – 方法methods混入action事务
- createNamespacedHelpers – 创建基于命名空间的辅助函数,是一个包含指定命名空间的mapState,mapGetters,mapMutations,mapActions对象
源码实现
Vuex安装注册
- Vue.use(Vuex) => 会触发Vuex的install方法
- Vuex中applyMixin方法:对于Vue版本进行判断,2.x版本中,Vue.mixin({beforeCreate: vuexInit})
- vuexInit方法:将this.$options.store注入到this.$store上
Store实例化 [Vuex依赖于promise]
new ModuleCollection(options)
- ModuleCollection.register(path, rawModule)
- new Module(rawModule):每个module模块
- 对于刚开始,path为空数组,会执行this.root = newModule
rawModule.modules也就是在new Vuex.Store时传递的modules数据,如果有值,就会递归运行如下[将modules建立成一个树状结构]
1
2
3
4// this指向ModuleCollection,path是一直贯穿的数据
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime);
})path已经不再是空数组,会执行以下代码
1
2
3
4
5
6
7
8// this指向ModuleCollection,建立父子关系
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
// parent是module实例,parent.addChild方法如下[this指向module]
addChild(key, module) {
this._children[key] = module
}
installModule(this, state, [], _this.module.root)
上面数据state就是_this.module.root.state。installModule(state, rootState, path, module, hot)
对于mutations,actions,getters,_wrapperGetters等初始化。遍历每个module的actions,mutations,getters,child数据
- 根据namespace设置_store._modulesNamespaceMap[namespace] = module。
- 设置每个module.context = makeLocalContext,可以避免重名问题,commit提交mutation时,不加namespace也可以成功。
- makeLocalContext返回的是一个local对象:dispatch,commit,getters。dispatch、commit根据传递的参数namespace + type形成新的type值,再调用store[methodName](type)。
mutation,action对应的commit(namespace/mutationName),dispathc(namespace/actionName),_mutation和_action一层对象,key值是含有一层层namespace;state对应的state[namespace]对象形式一层一层往下查找。
- forEachMutation 递归registerMutation
- forEachAction 递归registerAction,每个action(除了返回值是Promise类型的)被执行后返回的是一个Promise.resolve(action函数执行后结果)实例化后的Promise实例
- forEachGetter 递归registerGetter,每个getter会挂载在store._wrappedGetters上
- forEachChild 递归执行installModule,传递的path是:全局path一直concat(key)
resetStoreVM(this, state)
保证了Vuex的store中state存储数据是响应式,并且getters类似于Vue的computed,拥有缓存。
store.getters的每个getter做一个get拦截,拦截返回store._vm[key]:store._vm实例上对应的computed-key
store._vm是一个Vue实例,data数据$$state: state, computed其实就是传递的所有的getters
实现Vuex关键点
构造函数参数
- state响应式,利用new Vue({data: {$$state: state}})实例化Vue,使得state拥有和Vue一样的响应式。
- getters缓存计算,利用Vue实例的computed特性。
- mutations注意模块化,执行上下文利用call
- actions注意模块化,同时context传递的不是store本身,而是一个拥有相同dispatch,commit,getters,state,rootGetters,rootState数据的对象
- modules 递归实现
- 首先需要一个LocalContext
- 递归给rootState添加state形成一个树形状态管理
- 将getters直接挂载在store_wrappedGetters,Object.defineProperty(store.getters)get拦截返回vue实例compute,注册到store实例的getters属性
- 将mutations挂载在store._mutation,并且属性名称就是namespace + ‘/‘ + key,属性值是一个数组
- 将actions挂载在store._actions,属性名称是namspace + ‘/‘ + key,属性值是一个数组
- modules递归挂载在store._modules.root._children,一层一层形成一个树状结构
实例属性
- state 返回的是store实例上_vm._data.\$\$state,也就是Vue实例中data的$$state数据
- getters registerGetter时,将每个module提取到this._wrappedGetters对象中,在对store实例getters每个属性进行拦截时,Object.defineProperty中get返回this._wrappedGetters对应getter
实例方法
- dispatch
- commit
- subscribe 返回的是一个函数,运行后可以停止订阅。订阅mutation,在每个mutation完成后调用。
- subscribeAction 返回的是一个函数,运行后停止订阅。订阅action,在每个action分发调用。【before,after指定action分发之前之后】
- replaceState 替换整个store实例上的state数据
- watch 实际是this._watcherVM.$watch。返回函数用于停止监听。
- registerModule 注册模块。需要installModule,resetStoreVM
- unregisterModule 卸载模块。删除模块数据,同时将state也Vue.delete。根据最新数据installModule和resetStoreVM