vuex源码学习过程

205 阅读3分钟

一、 vuex是用来干什么的。

Vuex 作为一个专为 Vue.js 程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

二、vuex的工作流程

1649319558249.jpg

三、提出的问题

那么大家在开发的过程中有没有想过以下几个问题:

1、 Vuex 是怎么挂载到 Vue 上的呢

2、 为什么能够在所有子组件中使用 store

3、 Vuex 的数据响应式是如何实现的

4、 Vuex 的严格模式又是怎么回事

5、 为什么只能通过mutation修改state?

6、怎么实现非mutation修改时报错呢

7、辅助函数 mapState、mapGetters、mapActions、mapMutations作用

8、命名空间的实现

四、解决问题

(1)Vuex 是怎么挂载到 Vue 上的呢

通过new Vue实例化的过程

// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

(2) 为什么 state 能够在所有子组件中使用 store

Vue使用use调用插件,默认调用插件内部的install方法, install内部实现使vue每个组件都可以访问到store

// main.js
import Vue from 'vue'
import Vuex from './vuex.js'
Vue.use(Vuex) 
// vuex.js
const install = (v) => {
  Vue = v // 参数v是 Vue 构造器
  // 需要在每一个组件上添加store, this指的是当前的vue实例
  // 通过全局混入来添加一些组件选项。
  Vue.mixin({
    beforeCreate() {
        console.log(this.$options, 'sdddd')
      if (this.$options && this.$options.store) {
         // 根结点,默认有store
         this.$store = this.$options.store
      } else {
          // 非根节点,找到父组件的store,依次下去,保证了每个组件都有store实例
         this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
export default {
    install,
}

(3) Vuex 的数据响应式是如何实现的

因为vue的数据是响应式的,所以vuex借助vue响应式原理,实现数据响应式,因为vuex只适用于vue框架

// vuex.js
class Store {
  constructor(option) {
    // 以vue的方式,使store的state成为响应式的
    this.vm = new Vue({
       data: {
         state: option.state // state是对象,所有是引用类型,所以是响应式的
       } 
    })
  }
  // state es6
  get state() {
      return this.vm.state
  }

(4) Vuex 的严格模式又是怎么回事

开启严格模式,仅需在创建 store 的时候传入 strict: true

const store = new Store({
  // ...
  strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

(5) 为什么只能通过mutation修改state?

每一条 mutation修改需要被记录,devtools 都需要捕捉到前一状态和后一状态的快照。mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢。

mutation中异步修改state也能生效,但不建议,违背追踪原则

(6)怎么实现非mutation修改时报错呢

在mutation修改状态时,执行_withCommitting方法

class Store {
  constructor(option) {
    // 以vue的方式,使store的state成为响应式的
    this.vm = new Vue({
       data: {
         state: option.state // state是对象,所有是引用类型,所以是响应式的
       } 
    })
    this._committing =false // 标识
    this.strict = option.strict // 是否是严格模式
    if (this.strict) {
      // 只要状态一变化会立即执行,在状态变化后同步执行 sync, 此时_committing还是true
      // 以vue的方式创建一个监听,监听state的变化,判断其是否是通过mutation修改的,若不是,则报错提示
      this.vm.$watch(() => this.vm._data.state, () => {
        // console.assert() 断言方法在第一个参数为 false 的情况下会在控制台输出信息。
        console.assert(this._committing, '在mutation之外更改了')
      },  {deep: true, sync: true})
    }
    this.mutations = {}
    Object.keys(mutations).forEach((mutationName) => {
         // 在mutations上定义一个方法
         this.mutations[mutationName] = payload => {
            this._withCommitting(() => {
              mutations[mutationName](this.state, payload) // 这里执行更改state状态
            })
         }
    })
  }
  // 切片
  _withCommitting(fn) {
    this._committing = true
    fn()
    this._committing = false
  }

(7) 辅助函数 mapState、mapGetters、mapActions、mapMutations作用

可以快速的通过key拿到对应的值

import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
computed: {
  ...mapState([
     'count' // 映射 this.count 为 store.state.count
  ]),
  // 使用对象展开运算符将 getter 混入 computed 对象中
  ...mapGetters([
     'doneTodosCount',
     'anotherGetter',
  ])
},
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')`
    }),
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
}

(8)module

1、模块默认是全局空间, 即namespaced: false

2、状态不要和模块的名字相同,相同时会优先取模块的

3、默认计算属性直接通过getter获取,即全局空间的getter是自动合并的

4、如果增加namespaced:true,会将这个模块的所有属性都放到这个作用域下,否则会放到父模块作用域下

5、默认会找到当前模块上是否有namespace,并且将父级的namespaced一同算上,做成命名空间