Vuex 2.0 学习笔记(二):源码阅读

上一章总结了 Vuex 的框架原理,这一章我们将从 Vuex 的入口文件开始,分步骤阅读和解析源码。由于 Vuex 的源码是基于 Vue 和 ES6 的,因此这两部分不熟悉的小伙伴可以先去熟悉一下它们的基础知识,这对理解 Vuex 源码的实现原理帮助是很大的。

在阅读源码之前,我们需要到 Vuex 的 Github 地址下载源码及示例代码:

git clone https://github.com/vuejs/vuex.git

1. 目录结构介绍

虽然下载下来的代码有很多个文件夹,但是源码只被存在./src目录下,具体的源码目录结构如下图:

Vuex 源码目录结构

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 等方法。

2. 初始化装载与注入

在基本了解了源码的目录结构之后,就可以开始源码的阅读了。我们先从入口文件index.js开始入手,index.js中包含了所有的核心代码的引用。

2.1 装载实例

在将 Vuex 注入到一个 Vue 项目时,我们经常会在项目的store.js文件中加载 Vuex 框架,并且创建出一个 store 对象实例:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store()

然后在index.js中,正常初始化一个页面根级别的 Vue 组件,传入这个自定义的 store 对象:

import Vue from 'vue'
import App from './../pages/app.vue'
import store from './store.js'

new Vue({
  el: '#root',
  router,
  store, 
  render: h => h(App)
})

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

2.2 装载分析

store.js文件的代码开头中,let Vue语句定义了局部 Vue 变量,用于判断是否已经装载和减少全局作用域查找。

接着判断是否已经加载过 Vue 对象,如果处于浏览器环境下且加载过 Vue,则执行 install 方法。install 方法将 Vuex 装载到 Vue 对象上,Vue.use(Vuex)也是通过它执行:

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)  
}

若是首次加载,将局部 Vue 变量赋值为全局的 Vue 对象,并执行mixin.js文件中定义的 applyMixin 方法:

function install (_Vue) {
  if (Vue) { //保证install方法只执行一次
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue) // => ./minxin.js
}

点开mixin.js文件,看一下 applyMixin 方法内部代码,由于 Vue 2.0 以后引入了一些生命钩子,因此这里也对 Vue 的版本进行了判断:如果是 2.x.x 以上版本,可以使用 hook 的形式进行注入,或使用封装并替换 Vue 对象原型的 _init 方法,实现注入:

const version = Number(Vue.version.split('.')[0])

if (version >= 2) {
  Vue.mixin({ beforeCreate: vuexInit })
} else {
  const _init = Vue.prototype._init
  Vue.prototype._init = function (options = {}) {
    options.init = options.init
      ? [vuexInit].concat(options.init)
      : vuexInit
    _init.call(this, options)
  }
}

我们再看一下具体的注入方法:

// 初始化钩子前插入一段 Vuex 初始化代码 => 给 Vue 实例注入一个 $store 属性 => this.$store.xxx
function vuexInit () {
  const options = this.$options
  if (options.store) {
    this.$store = options.store
  } else if (options.parent && options.parent.$store) {
    this.$store = options.parent.$store
  }
}

从代码中可以看出来,vuexInit()方法将初始化 Vue 根组件时传入的 store 设置到 this 对象的 $store 属性上,子组件从其父组件引用 $store 属性,层层嵌套进行设置。在任意组件中执行 this.$store 都能找到装载的那个 store 对象。

如果觉得这么说看起来有些抽象,那就画个对应 store 的流向图吧:

store 流向图

3. store 对象构造

上面对Vuex框架的装载以及注入自定义store对象进行分析,接下来详细分析store对象的内部功能和具体实现。

3.1 环境判断

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

assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)

通过这两行的判断语句,我们可以看出在 store 构造函数中执行环境判断,以下都是Vuex工作的必要条件:

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

assert 函数是一个简单的断言函数的实现,类似于console.assert(),它的具体实现在util.js文件中:

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

3.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 类,下面看看其功能:

this.root = new Module(rawRootModule, false)

if (rawRootModule.modules) {
  forEachValue(rawRootModule.modules, (rawModule, key) => {
    this.register([key], rawModule, false)
  })
}

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

3.3 dispatch与commit设置

在 store 的构造函数中:

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) // 配置参数处理

  // 当前type下所有action处理函数集合
  const entry = this._actions[type]
  if (!entry) {
    console.error(`[vuex] unknown action type: ${type}`)
    return
  }
  return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
}

前面提到,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) {
    console.error(`[vuex] unknown mutation type: ${type}`)
    return
  }
  // 专用修改state方法,其他修改state方法均是非法修改
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })

  // 订阅者函数遍历执行,传入当前的mutation对象和当前的state
  this._subscribers.forEach(sub => sub(mutation, this.state))

  if (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 选项则进行提示警告。

3.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 状态还原为之前的状态。

3.5 module 安装

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

// strict mode
this.strict = strict

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

3.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 (namespace) {
    store._modulesNamespaceMap[namespace] = module
  }

  // 非根组件设置 state 方法
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  ...
}

判断是否是根目录,以及是否设置了命名空间,若存在则在 namespace 中进行 module 的存储,在不是根组件且不是 hot 条件的情况下,通过 getNestedState 方法拿到该 module 父级的 state,拿到其所在的 moduleName ,调用Vue.set(parentState, moduleName, module.state)方法将其state设置到父级 state 对象的 moduleName 属性中,由此实现该模块的 state 注册(首次执行这里,因为是根目录注册,所以并不会执行该条件中的方法)。getNestedState 方法代码很简单,分析 path 拿到 state。

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

3.5.2 module 上下文环境设置

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

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

3.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 操作。

3.5.4 子 module 安装

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

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

3.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 实时访问的。

3.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 修改,则报错。

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

源码分析到这里,Vuex 框架的实现原理基本都已经分析完毕。

总结

讲道理,读源码是个比较痛苦的过程,不过在通读源码之后,也会有豁然开朗的感觉。我在这里放五个美团点评团队关于 Vuex 的问题思考,希望能起到一个总结的作用吧。

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

Vue.use(Vuex)方法执行的是 install 方法,它实现了 Vue 实例对象的 init 方法封装和注入,使传入的 store 对象被设置到 Vue 上下文环境的 $store 中。因此在 Vue Component 任意地方都能够通过this.$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实现。

源码中还有一些工具函数类似 registerModule、unregisterModule、hotUpdate、watch 以及 subscribe 等,如有兴趣可以打开源码看看,这里不再细述。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Vuex 是一个专为 Vue 服务,用于管理页面数据状态、提供统一数据操作的生态系统。它专注于 MVC 模式中的 ...
    你的肖同学阅读 2,132评论 7 35
  • 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue...
    染陌同学阅读 1,668评论 0 12
  • Vuex 是什么? ** 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式**。它采用集中...
    Rz______阅读 2,300评论 1 10
  • vuex 场景重现:一个用户在注册页面注册了手机号码,跳转到登录页面也想拿到这个手机号码,你可以通过vue的组件化...
    sunny519111阅读 8,010评论 4 111
  • 系列文章:Vue 2.0 升(cai)级(keng)之旅Vuex — The core of Vue applic...
    6ed7563919d4阅读 4,544评论 2 58