美文网首页
Vuex 的构建原理

Vuex 的构建原理

作者: anarkhz | 来源:发表于2019-06-15 19:59 被阅读0次

解析源码前,我们先来简单的了解下Vuex。
Vuex 是为 Vue 量身定做的状态管理状态管理模式,目的是解决多个组件共享状态带来的复杂数据流难以为维护的问题。简单的说,就是在多个组件间更工程化、简洁明了的共享数据处理。
vuex模式图

一、项目目录

首先,让我先浏览下项目目录。如下:

   |— src  源代码
       |— module  模块相关
            |—module-collection.js
            |—module.js
        |— plugins
            |— devtool.js
            |— logger.js
        |— helpers.js  辅助函数
        |— index.esm.js  入口文件,使用ES Module
        |— index.js  入口文件
        |— mixin.js  混淆
        |— store.js  核心模块
        |— util.js  工具函数

二、入口

接着,我们从入口文件 index.js 开始,逐级的去了解 Vuex 的构建。

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

在入口文件中,我们看到 vuex 这里导出了一个 object,之中包含了核心模块Store,装载模块方法install,版本version,辅助函数mapState、mapMutations、mapGetters、mapActions,命名空间辅助函数createNamespacedHelpers。

三、注入安装

我们的都知道,Vue.js提供了Vue.use方法用来给Vue.js安装插件,通过调用插件内部的 install 方法来安装的(如果插件是对象)。
我们来看这里 install 的实现。

 function install (_Vue) {
  if (Vue && _Vue === Vue) {  // 检测 Vue 是否存在,且和已安装的是否相同,防止重复安装
    if (process.env.NODE_ENV !== 'production') {  // 判断是开发环境,输出日志到控制台
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue  // 保存当前 Vue
  applyMixin(Vue)  // 调用 applyMixin 将 Vuex 混入 Vue
}

让我们再来看下 applyMixin,其实在mixin.js 中只实现了一个混淆,就是将 vuex 混淆注入 vue。

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])  // 获取 vue 的版本号首位

  if (version >= 2) {
    // 如果 vue 版本2.0以上,调用vue.mixin,在 beforeCreate 的时候执行 vuexInit 注入 vuex
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // 如果 vue 1.x 的版本,将 vuexInit 放入 vue 的 _init 方法中
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit () {
    const options = this.$options
    // 获取实例上的 store
    if (options.store) {
      // 如果 store 是个函数,执行它获得 store
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      // 当前组件上没有 store,就从父组件上获取 store
      this.$store = options.parent.$store
    }
  }
}

这里对于 vue 做了区分,在 vue 2 以上的版本调用 Vue.mixin 直接混入到 beforeCreate 钩子中,1.0 放入 _init 方法中。
在 vuexInit 方法中,获取当前组件 store,如果不存在再从父组件获取,保证了 store 的一致性,同时把 _this.$store 指向跟 store 实例。

四、Store

(1)构造函数

constructor (options = {}) {
    // 判断注入vuex
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    
    // 非生产环境输出日志
    if (process.env.NODE_ENV !== 'production') {
      // 判断 Vuex 是否已混入 Vue
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      // 判断是否支持 Promise
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      // 判断是否需要使用 new 关键字来创建 Store 实例
      assert(this instanceof Store, `store must be called with the new operator.`)
    }
    
    const {
      plugins = [],  // Vuex 的插件方法,接收 Store 作为唯一参数
      strict = false  // 严格模式,在严格模式下,通过提交 mutation 之外的方法改变 state 都会报错
    } = options

    this._committing = false  // 是否正在commit
    this._actions = Object.create(null)  // actions
    this._actionSubscribers = []  // actions 订阅者
    this._mutations = Object.create(null) // mutations
    this._wrappedGetters = Object.create(null) // getters
    this._modules = new ModuleCollection(options) // modules收集器
    this._modulesNamespaceMap = Object.create(null) // 根据命名空间收集modules
    this._subscribers = []  // 订阅者
    this._watcherVM = new Vue()  // 观察者 Vue 实例

    // 把 commit 和 dispatch 绑定到 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)
    }

    // 严格模式(严格模式下,state 只能通过提交 mutations 来更改,否则会抛出错误)
    this.strict = strict

    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化根模块,递归注册依赖的模块,收集所有模块getters放进_wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 初始化store vm,注册 getters 和计算属性。
    resetStoreVM(this, state)

    // 调用插件
    plugins.forEach(plugin => plugin(this))

    // devtool插件
    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }

可以看出来,构造器函数主要做了这几件事情:
1、声明基础变量
2、创建 modules 收集器 、调用 installModule 初始化并且递归注册子模块
3、调用 resetStoreVM 来使 Store 具有“响应式”

(2)模块的加工

首先,我们先来看看 ModuleCollection 是怎样收集保存为 _modules 的。

export default class ModuleCollection {
  constructor (rawRootModule) {
    // 注册模块
    this.register([], rawRootModule, false)
  }
   
  // 根据 path 来获取模块
  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

  // 根据 path 来获取命名空间
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }

  // 更新 modules
  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }

  // 注册方法
  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      // 判断模块是否符合规范
      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)
    }

    // 递归的注册关联模块
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
  
  // 注销子模块
  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return

    parent.removeChild(key)
  }
}

可以看到,ModuleCollection 主要是将传入的 options 加工为可用的 _modules 模块,提供了一些方法。
主要做了以下四件事:
1、判断模块是否符合规范
2、封装模块,并递归的封装子模块挂载到父模块上,把所有模块种成一颗_modules树
3、提供一些模块可用方法

接着,我们再看看一个 module 是怎样实例化的吧。

export default class Module {
  constructor (rawModule, runtime) {
    // 是否正在处理
    this.runtime = runtime
    // 创建 _children 容器,保存子模块
    this._children = Object.create(null)
    // 保存未处理的模块
    this._rawModule = rawModule
    // 保存此模块state
    const rawState = rawModule.state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  // 是否有命名空间
  get namespaced () {
    return !!this._rawModule.namespaced
  }

  // 添加子模块
  addChild (key, module) {
    this._children[key] = module
  }

  // 移除子模块
  removeChild (key) {
    delete this._children[key]
  }

  // 获取子模块
  getChild (key) {
    return this._children[key]
  }
  
  // 更新保存的_rawModule
  update (rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }
    
  // 遍历子模块
  forEachChild (fn) {
    forEachValue(this._children, fn)
  }

  // 遍历getters
  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

  // 遍历actions
  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  // 遍历mutations
  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

主要做了以下几件事:
1、定义 _rawModule 、state 等基础属性
2、定义 namespaced 属性来获取模块的 namespaced,判断是否有命名空间
3、提供模块的一些基础方法,添加、删除、获取和更新
4、提供遍历的工具方法

(3)模块的注册

平时我们使用 mutations,actions 的时候,只需向 commit/dispatch 传入一个字符串和载荷,就可以更改 state。
那么,vuex 是如何做到的呢。这就轮到 installModule 出马了。

// installModule 是一个私有方法,并不在原型上。
function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length  // 判断根模块,path 不存在即根模块
  const namespace = store._modules.getNamespace(path)  // 获取模块的命名空间

  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]
    // 将子模块的 state 挂载到父模块上
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  // 获取执行上下文
  const local = module.context = makeLocalContext(store, namespace, path)

  // 注册 mutaions,actions,getters
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

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

installModule的意义是初始化根模块然后递归的初始化所有模块,并且收集模块树的所有getters、actions、mutations、以及state。
主要做了这些事:
1、根据命名空间收集 getters、actions、mutations
2、获取执行上下文来注册 getters、actions、mutations(下面详说)
3、递归的注册子模块的 getters、actions、mutations

(4)dispatch 和 commit 的实现

我们都知道,dispatch 和 commit 有两种提交风格:

// 以 commit 为例
// 标准风格
this.$store.commit('myMutation',{name : 'Shein'});
// 对象风格
this.$store.commit({type: 'myMutation',name : 'Shein'});

所以第一步要统一我们的参数

function unifyObjectStyle (type, payload, options) {
  //  判断我们传入的第一个参数是否为对象,
  if (isObject(type) && type.type) {
    options = payload  // options 设为第二个参数
    payload = type  // 载荷设置为第一个参数
    type = type.type  // mutation 为 type 值
  }

  if (process.env.NODE_ENV !== 'production') {
    // mutation 方法名不为 string 时,抛出错误
    assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

现在,我们来看看 commit 函数

  commit (_type, _payload, _options) {
    // 校正参数并定义
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    
    const mutation = { type, payload }
    const entry = this._mutations[type]  // 获取 mutation 入口
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      // 遍历入口,执行 mutation
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))  // 订阅者

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

commit 首先矫正了参数,然后再去通过 this._mutations[type] 获取命名空间中所有的 mutations 进行遍历,执行 handle。
那么 dispatch 呢?

dispatch (_type, _payload) {
    // 同样是先校正参数
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]  // 获得入口
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      // 订阅者
      this._actionSubscribers
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1  // 入口大于1,使用Promist.all
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return result.then(res => {
      try {
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      return res
    })
  }

与 commit 大同小异,不同的是,在多个同名 actions 存在时候,内部是调用 Promise.all 并发执行的。
看完了 commit 和 dispatch 是怎么触发 mutation 和 action 之后,不禁要问,mutation 和 action 是如何执行的,内部又是如何调用的呢?
以 mutation 为例,再让我们回头看看 mutations 是如何注册的。

module.forEachMutation((mutation, key) => {
   // 遍历并注册模块内部的 mutaions
  const namespacedType = namespace + key
  registerMutation(store, namespacedType, mutation, local)
})

我们把 registerMutation 方法抓出来看一下

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

首先通过 type,获取到了所有同名方法的入口(没有的话会新建一个入口),再将执行方法 handler push 到入口中,这里使用了 call 方法,把 this 指向到 store. mutation 接收两个参数,分别是 state 和 payload。
我们再来看下 action 的注册。

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      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)  // 非 promise 转为 promise
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

和 commit 不同,action 的第一个参数会传入 context ,所以在 registerAction 中会通过 local 把dispatch 等方法传进来。其次,如果 actions 不是 Promise,会强制转为 promise,是为了多个同名 action 并发的时候,使用 Promise.all() 来处理。
那么问题来了,local 有什么用?是怎么定义的呢?
把视线拉回 local 定义的地方。

  const local = module.context = makeLocalContext(store, namespace, path)
   …………
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    // 不启用命名空间,走向 store 的dispatch,启用命名空间,走向模块内部 dispatch。
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      
      // 判断 options.root 不存在,为分发的 action 加上命名空间
      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

    // commit 和 dispatch 同理
    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    // 定义 getters,存在命名空间则通过 makeLocalGetters 代理到模块内部的 getters
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      // 获取到模块内部 state
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

local 里会判断是否有命名空间,有的话,返回模块内部的 dispatch、commit 和 getters,state 固定返回模块的 state。
我们再来看看 makeLocalGetters 怎么获取局部 getters。

function makeLocalGetters (store, namespace) {
   // 定义代理对象
  const gettersProxy = {}
  // 定义切割点
  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    // 跳过不匹配的getter
    if (type.slice(0, splitPos) !== namespace) return

    // 获取局部 getter
    const localType = type.slice(splitPos)

    // 定义代理对象 localType
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })
  // 返回代理对象
  return gettersProxy
}

创建局部的getters就是一个代理的过程,在使用模块内使用(没有加上命名空间的)getters的名字,会被代理到,store实例上那个真正的(全名的)getters。
最后,来看一下 getNestedState

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

比较简单了,通过 path 获取当前模块的 state。
local 到此已经定义完毕,那么它是用来做什么的呢。

  1. 注册 mutations
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

在注册 mutation 的时候,我们传入的是模块内部的state。
2.注册 actions

function registerAction (store, type, handler, local) {
  ……
  let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
  ……
}

action 中传入的 context,都会被代理到模块本身的 action 上。

  1. 注册getters
function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

在 getters 中,我们可以任意的组装数据,既可以获取局部的 state 、getters ,也可以获取全局的。
到此,模块相关的已经告一段落。接下来我们来看下 Vuex 怎么依赖Vue核心实现数据的“响应式化”。

(5)响应式更新数据 resetStoreVM

function resetStoreVM (store, state, hot) {
  // 保存旧实例
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  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 enviroment.
    // 遍历 wrappedGetters ,为每个 getters 设置 get,映射到 store.vm[key]
    computed[key] = partial(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
  /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告 */
  const silent = Vue.config.silent
  Vue.config.silent = true
  /*  这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  /* 使能严格模式,保证修改store只能通过mutation */
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    /* 解除旧vm的state的引用,以及销毁旧的Vue对象 */
    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())
  }
}

resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每一个getter绑定上get方法,这样我们就可以在组件里访问this.$store.getter.test就等同于访问store._vm.test。

之后Vuex采用了new一个Vue对象来实现数据的“响应式化”,运用Vue.js内部提供的数据双向绑定功能来实现store的数据与视图的同步更新。

这两步执行完以后,我们就可以通过this.$store.getter.test访问vm中的test属性了。

五、总结

Vuex 主要原理构建的解析就到此结束啦。
总的来说,Vuex 的代码不是很多。但麻雀虽小,五脏俱全,里面的设计模式还是十分优秀,值得我们学习的。我总结几点,在项目中可能会遇到的情况吧(其实文档里大都有写- -):
1、模块内提交 mutation 和 action,需要更改root状态,或者提交root的commit,传入options: {root: true}。
2、没有声明严格模式的情况下,state 可以被直接修改的,需要注意⚠️,声明了严格模式后,任何非提交 mutation 的对 state 都会抛出错误,这能保证所有的状态变更都能被调试工具跟踪到。
3、分发 action 依然会存在“竞态”的问题,需要注意业务逻辑的先后,之所以要分发 action ,是为了每个 mutation 提交后会立即得到一个新的状态。
4、如果不设置namespaced: true,不同模块相同的 mutation 可以同时触发,相同模块同名 mutation 后面的会覆盖前面的。actions 同理。

还有,最好不要用 360 查杀 Vuex 的项目。会报毒!!!!



ENDING....

相关文章

  • Vuex 的构建原理

    一、项目目录 首先,让我先浏览下项目目录。如下: 二、入口 接着,我们从入口文件 index.js 开始,逐级的去...

  • 教你快速明白和搭建Vuex工作环境

    vuex工作工作原理(写给自己看的笔记以加深自己的理解) 一、Vuex工作原理 首先我们先来了解下Vuex: 1...

  • vuex入门教程

    vuex (一)vuex是什么?Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式(二)适用场景?构建...

  • vuex 原理解析

    vuex的原理关键:使用vue实例管理状态

  • VUEX的原理

    vuex可以理解成是转为vuejs应用开发的全局状态管理功能,它让状态以一种可被追踪的形式进行变更,方便代码维护。...

  • vuex原理

    https://github.com/answershuto/learnVue/tree/master/docs ...

  • Vuex 原理

    1、Vue.use(Vuex):将Vuex 应用到Vue中 use是一个自定义插件,这个插件里有一个固定方法ins...

  • vuex 原理

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应...

  • vuex源码分析(一)——初始化vuex

    本文参考珠峰架构公开课之vuex实现原理 vuex是基于vue框架的状态管理。 如何在vue项目中初始化vuex?...

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

    上一章总结了 Vuex 的框架原理,这一章我们将从 Vuex 的入口文件开始,分步骤阅读和解析源码。由于 Vuex...

网友评论

      本文标题:Vuex 的构建原理

      本文链接:https://www.haomeiwen.com/subject/yikvnqtx.html