在日常的开发过程中,我们存储数据状态的方式有好多种方式,localstroage sessionStorage vuex 和react-redux接下来我们着重了解一下vuex,本文主要对于vuex3.0的版本
vuex使用
概念:vuex是一个专门为vue应用开发的状态管理模式,它采用集中式存储管理应用所有组件的状态,并以相应的规则保证状态以一种可以预测的方式发生变化 —-来自官方vuex
个人理解:vuex是进行数据的管理和共享,以达到多组件数据公用和数据通信的状态模式。
vuex的属性:state(数据状态) mutations(同步执行函数) actions(异步执行函数) getters(数据状态缓存获取) module(模块)
state
state中定义共享的数据。
vue.use(Vuex)
export default new vuex.store({
state:{
userInfo:{
name:"张三",
age:12,
address:"中国"
}
}
})
复制代码
使用的时候 可以通过计算属性获取(状态值变化后自动更新) 也可以直接使用 或者使用辅助函数mapState()
//test.vue
export defalut {
data(){
retrun {
name:this.$store.state.userInfo.name
}
},
created(){
console.log(this.userInfo.address)
},
computed:{
getAge(){
return this.$store.state.userInfo.age
},
...mapState(['userInfo']) //当组件中数据的属性值和state中数据的属性值一样的时候可以这样写还可以 ...mapState({testaddress:'userInfo'}) 或者...mapState({testaddress:state=>state.userInfo})
}
}
复制代码
getters(同组件中的computed)
getters派生出state的值。
vue.use(Vuex)
export default new vuex.store({
state:{
userInfo:{
name:"张三",
age:12,
address:"中国"
}
},
getters:{
gethandleName(state){
return state.userInfo.name+'我是派生的name'
}
}
})
复制代码
使用同state的使用方式 mapGetters this.$store.getters.userInfo.name.
mutations
mutations中可以通过同步函数的方式去修改state中的值
vue.use(Vuex)
export default new vuex.store({
state:{
userInfo:{
name:"张三",
age:12,
address:"中国"
}
},
mutations:{
changeName(state,params){
console.log()
state.userInfo.name='我是改变后的name值'
}
}
})
复制代码
//使用 通过this.$store.commit('函数名称',参数),对象方式:this.$store.commit({type:"函数名称",name:12}) 还有辅助函数
//test.vue
export defalut {
data(){
retrun {
name:this.$store.state.userInfo.name
}
},
created(){
console.log(this.userInfo.address)
this.$store.commit({ //changeName函数 获取的参数则是{ //changeName函数 获取的参数则是 type:"changeName", tea:"eeee" }
type:"changeName",
tea:"eeee"
})
this.$store.commit('changeName',[2,3]) //changeName函数获取的传参则是[2,3]
//
},
}
复制代码
辅助函数mapMutations()
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
create(){
this.increment()
this.incrementBy('参数')
this.add()//等同于调用increament()
}
复制代码
action
action中执行异步的操作 在action中的异步函数中不直接的去修改state的值而是通过同步mutations中的函数去进行修改。在action中过滤掉异步请求 然后调用mutations中的方法同步的去修改state的值,如果直接在action中去修改state的值的话,因为异步的原因会导致我们修改的值的顺序(异步正在修改,其他地方同步修改问题)造成紊乱 造成系统的架构不稳定
vue.use(Vuex)
export default new vuex.store({
state:{
userInfo:{
name:"张三",
age:12,
address:"中国"
}
},
mutations:{
changeName(state,asycnName){
state.userInfo.name=asycnName
},
changeAge(state,asycnage){
state.userInfo.age=asycnage
}
},
actions:{
//异步修改名称
asyncChangeName(context,params){ //context是上下文对象 有state gettters commit属性
setTimeout(()=>{
context.commit('changeName','我是异步修改名字')
},1000)
}
//组合action 和promise进行组合
asycChangeAge ({ commit,state },params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation',params)
resolve(state.userInfo.age)
}, 1000)
})
//在另一个actin中组合调用
actionB ({ dispatch, commit }) {
return dispatch('asycChangeAge',18).then(() => {
})
}
}
})
复制代码
//使用
//test.vue
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
},
create(){
this.$store.dispatch('asyncChangeName',10) //也支持对象的方式 this.$store.dispatch({type:'asyncChangeName',numner:10})
//组合调用
this.$store.dispatch('asyncChangeAge',18).then((res) => {
console.log(res) //18
// ...
})
}
复制代码
辅助函数mapActions()
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
create(){
this.increment()
this.incrementBy('参数')
this.add()//等同于调用increament()
}
复制代码
module
module解决我们状态树庞大的问题,我们可以将不同业务的数据 放在不同module下 对于store进行一个module的分隔,每个module拥有自己的state mutations actions getters和子module
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
this.$store.state.a // -> moduleA 的状态
this.$store.state.b // -> moduleB 的状态
复制代码
子module中的getters和mutations和父级module的第一个参数一样都是对应module的state,action第一参数也是对应module的context对象但是子module中context多了一个rootState的属性代表父级的state数据,getters中的第三个参数才是rootState,第二个参数是父子所有个getters
命名空间
没有开启命名空间的前提下vuex默认会将子模块中的mutations actions 注册到全局命名空间,如果我们通过namespace:true的方式去开始子模块的命名空间的时候,则解决了因不同模块中间存在相同名称的方法 造成的冲突问题,在访问mutations和action中的方法的时候,得在方法名前添加模块名称
//app.js
export default{
namespaced:true,
state:{
app:"测试app",
status:"运行中"
},
mutations: {
changeApp(state,params,other){
console.log(state,params,other)
}
},
getters:{
changeAPPs(state,getters,other){
console.log(state,getters,other)
return 'wef'
}
},
actions: { },
}
复制代码
//使用
import {mapMutations,mapGetters,mapActions} from 'vuex'
methods:{
...mapMutations(['app/changeApp']),
...mapActions(['模块名称/函数名'])
},
created(){
this.$store.commit('app/changeApp')
this['changeAPPs']//使用
this.$store.gettes['changeAPPs']
},
computed:{
...mapGetters(['changeAPPs'])
}
复制代码
vuex源码解析
vuex是为了vue而实现的状态管理机制。因此我们在使用的时候通过vuex.use()方法去加载使用。vue.use()使用的时候会默认运行install方法,因此我们从install方法进入。
install中存在applyMixin()函数作为vuex流程的主入口
//store.js
export function install (_Vue) {
// 校验vue已经被挂载,却传入和Vue相同 => 已经use过了
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
// 开始主流程 做混入操作
applyMixin(Vue)
}
复制代码
对于每个组件实例都全局注册一个beforeCreate声明周期
//mixin.js
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit }) //
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
复制代码
vuexInit函数式将store注入到每个实例中,如果组件实例中存在store 则替换全局的this.store拿到的都是一个实例,这样才能实现数据的共享`
function vuexInit () {
const options = this.$options
// store injection
// store 注入到每个实例中
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
复制代码
//store.js
export class Store{
constructor(options){
// store internal state
this._committing = false
//存储自定义的actions
this._actions = Object.create(null)
this._actionSubscribers = []
//存储自定义的mutations
this._mutations = Object.create(null)
// 用来存储用户定义getters - 响应式
this._wrappedGetters = Object.create(null)
//进行模块的收集
this._modules = new ModuleCollection(options)
//创建命名空间的
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
// 响应式$watch
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
//获取到根状态
const state = this._modules.root.state
}
}
复制代码
自己调用自己的dispath和commit方法 防止以为this的指向不同进行错误的执行
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)
}
复制代码
installModule(this, state, [], this._modules.root) []代表的是命名空间的路径也就是命名空间的key
复制代码
命名空间的获取state的设置 mutations actions getters的注册,已经子模块的模块安装
//store.js
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
//获取命名空间
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
...
//添加到命名空间map
store._modulesNamespaceMap[namespace] = module
}
// set state
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('.')}"`
)
}
}
//设置在父级state中这是state 这也就是我们为什么 通过this.$store.state.命名空间.值的原因
Vue.set(parentState, moduleName, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
//注册mutations
registerMutation(store, namespacedType, mutation, local)
})
//注册actions
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
//注册getters
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
//有子模块的情况下 在进行模块的注册
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
复制代码
//mutations模块注册
function registerMutation (store, type, handler, local) { hanlder 是mutations中的函数
//命名空间
const entry = store._mutations[type] || (store._mutations[type] = [])
//将带有命名空间的mututions添加到命名空间数组中
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload) ///这也就是为什么mutations中函数第一个参数是state的原因
})
}
复制代码
设置响应式
//store.js
resetStoreVM(store, state, hot)
复制代码
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
//本地的gettes
const wrappedGetters = store._wrappedGetters
const computed = {}
//遍历getters 同时将每个getters添加到computd对象中
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
// 遍历地将所有getters桥接上store,并配置成computed属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
Vue.config.silent = true
// 通过vue实例化的方式将state和getters都转成响应式的对象
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 销毁 释放资源
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
复制代码
后续继续补充 详细的vuex可以看 若川的vuex源码共度