vuex框架原理和源码研究分析

大家一直使用vuex, 但是大多数很少了解到vuex的背后原理,我也很好奇它的背后原理,故上网看博客了解和下载Vuex源码研究分析。

vuex是管理页面数据状态的统一数据操作生态系统。修改数据要通过action mutation state change 的流程才能修改,保证数据的修改可控和方便排查数据流向。state变化后,vue根据双向数据绑定实现页面展示。

一、框架核心流程

vuex框架流程图:
vuex-core
如图:vue Compontents的数据变化依赖了vuex的完整生态。

  • Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
  • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态需要改变,则调用commit方法提交mutation修改state,通过getters获取到state新值,重新渲染Vue Components,界面随之更新。

二、vuex源码目录机构

在这里插入图片描述

  • *module: 提供module对象与module对象树的创建功能
  • *plugins: 提供开发辅助插件,如“时光穿梭”功能,state修改的日志记录功能等
  • *helpers.js:提供action、mutations以及getters的查找API;
  • *index.js:是源码主入口文件,提供store的各module构建安装;
  • *mixin.js:提供了store在Vue实例上的装载注入;
  • *util.js:提供了工具方法如find、deepCopy、forEachValue以及assert等方法。

三、初始化装载与注入

大家可以下载源码看看 下载vuex源码

3.1装载实例

先看看简单例子:
store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import { mutations, STORAGE_KEY } from './mutations'
import actions from './actions'
import plugins from './plugins'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    todos: JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]')
  },
  actions,
  mutations,
  plugins
})

store.js加载vuex, 创建并导出store对象实例。

vue-index.js:

import 'babel-polyfill'
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'

new Vue({
  store, // inject store to all children
  el: '#app',
  render: h => h(App)
})

vue-index.js初始化页面级的vue组件,并传入store对象。

3.2 装载分析

index.js文件代码执行开头,定义局部 Vue 变量,用于判断是否已经装载和减少全局作用域查找。

let Vue
Vue.use(Vuex) 到底做了什么呢?
// Vue.use方法实现:
export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}
 // store.js 里面构造方法执行
 // 然后判断若处于浏览器环境下且加载过Vue,则执行install方法。
 if (!Vue && typeof window !== 'undefined' && window.Vue) {
     install(window.Vue)
 }

install方法 有个applyMixin(vue)操作,将Vuex装载到Vue对象上,Vue.use(Vuex) 也是通过它执行。
若是首次加载,将局部Vue变量赋值为全局的Vue对象,并执行applyMixin方法,install实现如下:

// store.js 里面 install方法:
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (__DEV__) {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

具体实现:将初始化Vue根组件时传入的store设置到this对象的$store属性上,子组件从其父组件引用$store属性,层层嵌套进行设置。在任意组件中执行 this.$store 都能找到装载的那个store对象,vuexInit方法实现如下:

// applyMixin方法 注入store对象
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  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)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    const options = this.$options
    // store injection
    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流向:
在这里插入图片描述

四、store对象构造

store构造方法整体逻辑流程图:

在这里插入图片描述

4.1 环境判断

开始分析store的构造函数,分小节逐函数逐行的分析其功能。

export class Store {
  constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    if (__DEV__) {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }
    ...

在store构造函数中执行环境判断,以下都是Vuex工作的必要条件:

  1. 已经执行安装函数进行装载;
  2. 支持Promise语法。

assert函数是一个简单的断言函数的实现,一行代码即可实现。

function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

4.2 数据初始化、module树构造

环境判断后,根据new构造传入的options或默认值,初始化内部数据。

const {
    state = {},
    plugins = [],
    strict = false
} = options

// store internal state
this._committing = false // 是否在进行提交状态标识
this._actions = Object.create(null) // acitons操作对象
this._mutations = Object.create(null) // mutations操作对象
this._wrappedGetters = Object.create(null) // 封装后的getters集合对象
this._modules = new ModuleCollection(options) // Vuex支持store分模块传入,存储分析后的modules
this._modulesNamespaceMap = Object.create(null) // 模块命名空间map
this._subscribers = [] // 订阅函数集合,Vuex提供了subscribe功能
this._watcherVM = new Vue() // Vue组件用于watch监视变化

调用 new Vuex.store(options) 时传入的options对象,用于构造ModuleCollection类,下面看看其功能。 new ModuleCollection(options) 如下:

// module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
  ...
  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

ModuleCollection主要将传入的options对象整个构造为一个module对象,并循环调用 this.register([key], rawModule, false) 为其中的modules属性进行模块注册,使其都成为module对象,最后options对象被构造成一个完整的组件树。ModuleCollection类还提供了modules的更替功能,详细实现可以查看源文件module-collection.js。

4.3 dispatch与commit设置

继续回到store的构造函数代码。

// bind commit and dispatch to self
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)
}

封装替换原型中的dispatch和commit方法,将this指向当前store对象。dispatch和commit方法具体实现如下:

dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]  // 获取到对象action方法
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
	 // action订阅者前置方法调用
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return result.then(res => {
      try {
      // action订阅者后置方法调用
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (__DEV__) {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      return res
    })
  }

前面提到,dispatch的功能是触发并传递一些参数(payload)给对应type的action。因为其支持2种调用方法,所以在dispatch中,先进行参数的适配处理,然后判断action type是否存在,若存在就逐个执行(注:上面代码中的this._actions[type] 以及 下面的 this._mutations[type] 均是处理过的函数集合,具体内容留到后面进行分析)。

commit方法和dispatch相比虽然都是触发type,但是对应的处理却相对复杂,代码如下。

commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))

    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

该方法同样支持2种调用方法。先进行参数适配,判断触发mutation type,利用_withCommit方法执行本次批量触发mutation处理函数,并传入payload参数。执行完成后,通知所有_subscribers(订阅函数)本次操作的mutation对象以及当前的state状态,如果传入了已经移除的silent选项则进行提示警告。

4.4 state修改方法

_withCommit是一个代理方法,所有触发mutation的进行state修改的操作都经过它,由此来统一管理监控state状态的修改。实现代码如下。

_withCommit (fn) {
  // 保存之前的提交状态
  const committing = this._committing
    
  // 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告
  this._committing = true
    
  // 执行state的修改操作
  fn()
    
  // 修改完成,还原本次修改之前的状态
  this._committing = committing
}

缓存执行时的committing状态将当前状态设置为true后进行本次提交操作,待操作完毕后,将committing状态还原为之前的状态。

4.5 module安装

绑定dispatch和commit方法之后,进行严格模式的设置,以及模块的安装(installModule)。由于占用资源较多影响页面性能,严格模式建议只在开发模式开启,上线后需要关闭。

4.5.1 初始化rootState

上述代码的备注中,提到installModule方法初始化组件树根组件、注册所有子组件,并将其中所有的getters存储到this._wrappedGetters属性中,让我们看看其中的代码实现。

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    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('.')}"`
          )
        }
      }
      Vue.set(parentState, moduleName, module.state)
    })
  }
  ......

4.5.2 module上下文环境设置

const local = module.context = makeLocalContext(store, namespace, path)

命名空间和根目录条件判断完毕后,接下来定义local变量和module.context的值,执行makeLocalContext方法,为该module设置局部的 dispatch、commit方法以及getters和state(由于namespace的存在需要做兼容处理)。

4.5.3 mutations、actions以及getters注册

定义local环境后,循环注册我们在options中配置的action以及mutation等。逐个分析各注册函数之前,先看下模块间的逻辑关系流程图:

在这里插入图片描述

// 注册对应模块的mutation,供state修改使用
module.forEachMutation((mutation, key) => {
  const namespacedType = namespace + key
  registerMutation(store, namespacedType, mutation, local)
})

// 注册对应模块的action,供数据操作、提交mutation等异步操作使用
module.forEachAction((action, key) => {
  const namespacedType = namespace + key
  registerAction(store, namespacedType, action, local)
})

// 注册对应模块的getters,供state读取使用
module.forEachGetter((getter, key) => {
  const namespacedType = namespace + key
  registerGetter(store, namespacedType, getter, local)
})

registerMutation方法中,获取store中的对应mutation type的处理函数集合,将新的处理函数push进去。这里将我们设置在mutations type上对应的 handler 进行了封装,给原函数传入了state。在执行 commit(‘xxx’, payload) 的时候,type为 xxx 的mutation的所有handler都会接收到state以及payload,这就是在handler里面拿到state的原因。

function registerMutation (store, type, handler, local) {
  // 取出对应type的mutations-handler集合
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // commit实际调用的不是我们传入的handler,而是经过封装的
  entry.push(function wrappedMutationHandler (payload) {
    // 调用handler并将state传入
    handler(local.state, payload)
  })
}

action和getter的注册也是同理的,看一下代码(注:前面提到的 this.actions 以及 this.mutations在此处进行设置)。

function registerAction (store, type, handler, local) {
  // 取出对应type的actions-handler集合
  const entry = store._actions[type] || (store._actions[type] = [])
  // 存储新的封装过的action-handler
  entry.push(function wrappedActionHandler (payload, cb) {
    // 传入 state 等对象供我们原action-handler使用
    let res = handler({
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    // action需要支持promise进行链式调用,这里进行兼容处理
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

function registerGetter (store, type, rawGetter, local) {
  // getters只允许存在一个处理函数,若重复需要报错
  if (store._wrappedGetters[type]) {
    console.error(`[vuex] duplicate getter key: ${type}`)
    return
  }
  
  // 存储封装过的getters处理函数
  store._wrappedGetters[type] = function wrappedGetter (store) {
    // 为原getters传入对应状态
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

action handler比mutation handler以及getter wrapper多拿到dispatch和commit操作方法,因此action可以进行dispatch action和commit mutation操作。

4.5.4 子module安装

注册完了根组件的actions、mutations以及getters后,递归调用自身,为子组件注册其state,actions、mutations以及getters等。

module.forEachChild((child, key) => {
  installModule(store, rootState, path.concat(key), child, hot)
})

4.5.5 实例结合

前面介绍了dispatch和commit方法以及actions等的实现,下面结合一个官方的购物车实例中的部分代码来加深理解。

Vuex配置代码:

/
 *  store-index.js store配置文件
 *
 /

import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import cart from './modules/cart'
import products from './modules/products'
import createLogger from '../../../src/plugins/logger'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
  actions,
  getters,
  modules: {
    cart,
    products
  },
  strict: debug,
  plugins: debug ? [createLogger()] : []
})

Vuex组件module中各模块state配置代码部分:

/**
 *  cart.js
 *
 **/
 
const state = {
  added: [],
  checkoutStatus: null
}

/**
 *  products.js
 *
 **/
 
const state = {
  all: []
}

state结构图
在这里插入图片描述
state中的属性配置都是按照option配置中module path的规则来进行的,下面看action的操作实例。

Vuecart组件代码部分:

/**
 *  Cart.vue 省略template代码,只看script部分
 *
 **/
 
export default {
  methods: {
    // 购物车中的购买按钮,点击后会触发结算。源码中会调用 dispatch方法
    checkout (products) {
      this.$store.dispatch('checkout', products)
    }
  }
}

Vuexcart.js组件action配置代码部分:

const actions = {
  checkout ({ commit, state }, products) {
    const savedCartItems = [...state.added] // 存储添加到购物车的商品
    commit(types.CHECKOUT_REQUEST) // 设置提交结算状态
    shop.buyProducts( // 提交api请求,并传入成功与失败的cb-func
      products,
      () => commit(types.CHECKOUT_SUCCESS), // 请求返回成功则设置提交成功状态
      () => commit(types.CHECKOUT_FAILURE, { savedCartItems }) // 请求返回失败则设置提交失败状态
    )
  }
}

Vue组件中点击购买执行当前module的dispatch方法,传入type值为 ‘checkout’,payload值为 ‘products’,在源码中dispatch方法在所有注册过的actions中查找’checkout’的对应执行数组,取出循环执行。执行的是被封装过的被命名为wrappedActionHandler的方法,真正传入的checkout的执行函数在wrappedActionHandler这个方法中被执行,源码如下(注:前面贴过,这里再看一次):

function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  }

handler在这里就是传入的checkout函数,其执行需要的commit以及state就是在这里被传入,payload也传入了,在实例中对应接收的参数名为products。commit的执行也是同理的,实例中checkout还进行了一次commit操作,提交一次type值为types.CHECKOUT_REQUEST的修改,因为mutation名字是唯一的,这里进行了常量形式的调用,防止命名重复,执行跟源码分析中一致,调用 function wrappedMutationHandler (payload) { handler(local.state, payload) } 封装函数来实际调用配置的mutation方法。

看到完源码分析和上面的小实例,应该能理解dispatch action和commit mutation的工作原理了。接着看源码,看看getters是如何实现state实时访问的。

4.6 store._vm组件设置

执行完各module的install后,执行resetStoreVM方法,进行store组件的初始化。

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

综合前面的分析可以了解到,Vuex其实构建的就是一个名为store的vm组件,所有配置的state、actions、mutations以及getters都是其组件的属性,所有的操作都是对这个vm组件进行的。

一起看下resetStoreVM方法的内部实现。

function resetStoreVM (store, state) {
  const oldVm = store._vm // 缓存前vm组件

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  
  // 循环所有处理过的getters,并新建computed对象进行存储,通过Object.defineProperty方法为getters对象建立属性,使得我们通过this.$store.getters.xxxgetter能够访问到该getters
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    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设为静默模式,避免报出用户加载的某些插件触发的警告
  Vue.config.silent = true   
  // 设置新的storeVm,将当前初始化的state以及getters作为computed属性(刚刚遍历生成的)
  store._vm = new Vue({
    data: { state },
    computed
  })
  
  // 恢复Vue的模式
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    // 该方法对state执行$watch以禁止从mutation外部修改state
    enableStrictMode(store)
  }
  
  // 若不是初始化过程执行的该方法,将旧的组件state设置为null,强制更新所有监听者(watchers),待更新生效,DOM更新完成后,执行vm组件的destroy方法进行销毁,减少内存的占用
  if (oldVm) {
    // dispatch changes in all subscribed watchers
    // to force getter re-evaluation.
    store._withCommit(() => {
      oldVm.state = null
    })
    Vue.nextTick(() => oldVm.$destroy())
  }
}

resetStoreVm方法创建了当前store实例的_vm组件,至此store就创建完毕了。上面代码涉及到了严格模式的判断,看一下严格模式如何实现的。

function enableStrictMode (store) {
  store._vm.$watch('state', () => {
    assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
  }, { deep: true, sync: true })
}

很简单的应用,监视state的变化,如果没有通过 this._withCommit() 方法进行state修改,则报错。

4.7 plugin注入

最后执行plugin的植入。

plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))

devtoolPlugin提供的功能有3个:

// 1. 触发Vuex组件初始化的hook
devtoolHook.emit('vuex:init', store)

// 2. 提供“时空穿梭”功能,即state操作的前进和倒退
devtoolHook.on('vuex:travel-to-state', targetState => {
  store.replaceState(targetState)
})

// 3. mutation被执行时,触发hook,并提供被触发的mutation函数和当前的state状态
store.subscribe((mutation, state) => {
  devtoolHook.emit('vuex:mutation', mutation, state)
})

五、总结

  1. 问:使用Vuex只需执行 Vue.use(Vuex),并在Vue的配置中传入一个store对象的示例,store是如何实现注入的?

答:Vue.use(Vuex)
方法执行的是install方法,它实现了Vue实例对象的init方法封装和注入,使传入的store对象被设置到Vue上下文环境的 s t o r e 中 。 因 此 在 V u e C o m p o n e n t 任 意 地 方 都 能 够 通 过 t h i s . store中。因此在Vue Component任意地方都能够通过this. storeVueComponentthis.store访问到该store。

  1. 问:state内部支持模块配置和模块嵌套,如何实现的?

答:在store构造方法中有makeLocalContext方法,所有module都会有一个local
context,根据配置时的path进行匹配。所以执行如dispatch(‘submitOrder’,
payload)这类action时,默认的拿到都是module的local
state,如果要访问最外层或者是其他module的state,只能从rootState按照path路径逐步进行访问。

  1. 问:在执行dispatch触发action(commit同理)的时候,只需传入(type, payload),action执行函数中第一个参数store从哪里获取的?

答:store初始化时,所有配置的action和mutation以及getters均被封装过。在执行如dispatch(‘submitOrder’,
payload)的时候,actions中type为submitOrder的所有处理方法都是被封装后的,其第一个参数为当前的store对象,所以能够获取到
{ dispatch, commit, state, rootState } 等数据。

  1. 问:Vuex如何区分state是外部直接修改,还是通过mutation方法修改的?

答:Vuex中修改state的唯一渠道就是执行 commit(‘xx’, payload) 方法,其底层通过执行
this._withCommit(fn)
设置_committing标志变量为true,然后才能修改state,修改完毕还需要还原_committing变量。外部修改虽然能够直接修改state,但是并没有修改_committing标志位,所以只要watch一下state,state
change时判断是否_committing值为true,即可判断修改的合法性。

  1. 问:调试时的”时空穿梭”功能是如何实现的?

答:devtoolPlugin中提供了此功能。因为dev模式下所有的state change都会被记录下来,’时空穿梭’
功能其实就是将当前的state替换为记录中某个时刻的state状态,利用 store.replaceState(targetState)
方法将执行this._vm.state = state 实现。

最后感谢美团技术团队的vuex文章参考,vuex官方文档参考等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值