vuex的使用以及源码解析

386 阅读3分钟

在日常的开发过程中,我们存储数据状态的方式有好多种方式,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:12address:"中国"
    }
 }
})
使用的时候 可以通过计算属性获取(状态值变化后自动更新) 也可以直接使用 或者使用辅助函数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:12address:"中国"
    }
 },
 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:12address:"中国"
    }
 },
 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:12address:"中国"
    }
 },
 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若没有则使用父级组件的store保证我们在任何组件中通过this.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源码共度