[源码] vuex


导航 [[深入01] 执行上下文](https://juejin.im/post/684490...)
[[深入02] 原型链](https://juejin.im/post/684490...)
[[深入03] 继承](https://juejin.im/post/684490...)
[[深入04] 事件循环](https://juejin.im/post/684490...)
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深入08] 前端安全](https://juejin.im/post/684490...)
[[深入09] 深浅拷贝](https://juejin.im/post/684490...)
[[深入10] Debounce Throttle](https://juejin.im/post/684490...)
[[深入11] 前端路由](https://juejin.im/post/684490...)
[[深入12] 前端模块化](https://juejin.im/post/684490...)
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深入14] canvas](https://juejin.im/post/684490...)
[[深入15] webSocket](https://juejin.im/post/684490...)
[[深入16] webpack](https://juejin.im/post/684490...)
[[深入17] http 和 https](https://juejin.im/post/684490...)
[[深入18] CSS-interview](https://juejin.im/post/684490...)
[[深入19] 手写Promise](https://juejin.im/post/684490...)
[[深入20] 手写函数](https://juejin.im/post/684490...)
[[react] Hooks](https://juejin.im/post/684490...)
[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)
[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,访问,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)
[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)
前置知识 一些单词

Mutation:变异 raw:未加工 assert: 断言internal:内部的 ( store internal state : store内部的state )duplicate:重复,赋值,副本 localized:局部的unify:统一 ( unifyObjectStyle 统一对象类型 )Valid: 有效的

vue 如何编写一个插件
  • 插件通常来为 vue 添加 ( 全局功能 )
    • 全局方法, 全局资源, 全局混入, vue实例方法 等
    • Vue构造器静态方法和属性Vue.prototype实例方法和属性mixindirective
  • 如果使用插件
    • 通过组件形式:
        1. Vue.use(plugin, [options])
        1. new Vue(组件选项)
    • 通过script方式引入
      • 比如 vue-router插件
        • 会在 index.js 内部判断 如果是 ( 浏览器环境并且window.Vue存在 ),就自动调用 window.Vue.use(VueRouter)
  • 注意点:
    • 每个插件都应该暴露一个 install 方法,并且install方法的第一个参数必须是 Vue 构造器,第二个参数是一个可选的参数对象
      • 因为install方法的第一个参数是 Vue 构造器,所以可以获取Vue上的directive, mixin, 等等
    • 后面会知道如果没有暴露install方法,那插件本身就必须是一个函数,该函数就会被当做install方法
  • 代码
    插件:const firstPlugin = { install(Vue, options) { console.log(options, 'options')// 全局方法 firstGlobalMethod Vue.firstGlobalMethod = function () { console.log('插件 - 全局方法 - 即Vue构造器的静态方法') }// 添加全局资源 全局指令 Vue.directive('first-directive', { bind(el, binding, vnode, oldVnode) { console.log('只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置') console.log(el, 'el 指令所绑定的元素,可以用来直接操作DOM') console.log(binding, 'binding') console.log(vnode, 'Vue 编译生成的虚拟节点') console.log(oldVnode, '上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用') }, inserted() { console.log('被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)') } })// 全局混合 注入组件选项 Vue.mixin({ created: function () { console.log('插件 - 全局混合 - 在所有组件中混入created()生命周期') } })// 实例方法 // 一般规定在Vue.prototype上挂载的属性和方法都要以 $ 开头 Vue.prototype.$firstPluginMethod = function () { console.log('插件 - 实例方法 - 挂载在vue实例原型对象上的方法,记得该属性和方法以 $ 开头') } } }export default firstPlugin

    注册插件:import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import axios from 'axios' import firstFlugin from './plugin/first-plugin'Vue.config.productionTip = falseVue.prototype.$axios = axiosVue.use(firstFlugin) // -------------------------------------------------------------- 注册插件new Vue({ router, store, render: h => h(App) }).$mount('#app')

Vue.use()
  • Vue.use( plugin )
  • 参数:object 或者 function
  • 作用:
    • 安装Vue插件
    • 插件是对象:需提供 install 方法
    • 插件是函数:自身会作为 install 方法
    • install方法:会将 Vue 作为参数,在 Vue.use() 调用时自动执行
  • 注意:
    • Vue.use() 需要在 new Vue() 之前被调用
    • 当 install 方法被同一个插件多次调用,插件将只会被安装一次
  • Vue.use() 源码
    • 主要做了以下事情
      1. 如果插件注册过
        • 直接返回 ( 返回this主要为了能链式调用 )
      2. 没注册过
        • 判断该插件是对象还是函数
          • 对象:调用install方法
          • 函数:把该插件作为install方法,直接调用该函数
        • 把该插件添加进插件数组,下次判断是否注册过时,就注册过了
        • 返回 this 方便链式调用
      3. 总结:
        • Vue.use() 最主要就是调用插件的 install 方法
    // Vue.use() 源码 // 文件路径:core/global-api/use.jsimport { toArray } from '../util/index'export function initUse(Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { // Vue.use 在Vue构造器上挂载use方法const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // installedPlugins // 如果 Vue._installedPlugins 存在就直接赋值 // 如果 Vue._installedPlugins 不存在赋值为空数组if (installedPlugins.indexOf(plugin) > -1) { // 该插件在插件数组installedPlugins中存在就直接返回 // 这里返回 this,就可以实现链式调用,这里的this就是Vue return this }const args = toArray(arguments, 1) // additional parameters // 将除去Vue以外的参数收集成数组args.unshift(this) // 将Vue添加到数组的最前面 // 因为args要作为插件install方法的参数,而install方法规定第一个参数必须是Vue构造器 if (typeof plugin.install === 'function') { // 插件是对象,就把插件对象的install方法的this绑定到该plugin上,传入参数执行 plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // 插件是函数,传参执行 plugin.apply(null, args) }installedPlugins.push(plugin) // 如果该插件在installedPlugins不存在,首先在上面赋值为空数组,执行isntall后,把该插件添加进installedPlugins表示存在return this // return this 是为了链式调用,this指代 Vue } }// ------------------------------------------------------ // toArray(list, start) // 比如: // list = [1,2,3,4] // start = 1 export function toArray(list: any, start?: number): Array { start = start || 0 let i = list.length - start const ret: Array = new Array(i) while (i--) { ret[i] = list[i + start] // i-- 先赋值即 while(3) // 然后减去1即i=2 // ret[2] = list[3] // ret[1] = list[2] // ret[0] = list[1] // ret = [2,3,4] } return ret }

vuex源码 (1) vuex 如何安装和使用
  • 安装
    • npm install vuex -S
  • 使用
    main.js中 ----import Vuex from 'vuex' Vue.use(Vuex) // -------------------------------- 会调用Vuex上的install方法 const store = new Vuex.Store({ // --------------- new Store(options) state: {}, mutations: {}, actions: {}, getters: {}, modules: {} }) const app = new Vue({ router, store, // -------------------------------------- 注入store render: h => h(App) }).$mount('#app')

(2) Vue.use(Vuex)
  • Vue.use(Vuex) 会调用Vuex中的 Vuex.install 方法
    • Vuex.install 方法会调用 applyMixin(Vue)
      • applyMixin(Vue)
        • 主要就是把store实例子注入每个组件中
        • 具体就是在组件的的 beforeCreate 生命周期中mixin混入 vuexInit 方法,从而把store实例注入每个组件
          • mixin需要注意:
              1. 同名钩子函数将合并为一个数组,因此都将被调用
              1. 混入对象的钩子将在组件自身钩子之前调用
  • 总结:
    • Vue.use(Vuex) => Vuex.install() => applyMixin(Vue) => Vue.mixin({ beforeCreate: vuexInit })
    • 通过Vue.use(Vuex)的最终效果就是将store实例注入到每个组件中,且是共用同一个store实例
    • 所以:可以在每个组件中通过 this.$store 访问到 strore 实例
    Vuex中的install方法 ----let Vue // bind on 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相等,并且是生产环境,返回 } Vue = _Vue // Vue不存在,就赋值传入的参数_Vue applyMixin(Vue) // 调用 applyMixin 方法 }

applyMixin(Vue) --- export default function (Vue) { const version = Number(Vue.version.split('.')[0])if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }) // 版本大于等于2,混入 beforeCreate 生命周期钩子vuexInit方法 } else { // 版本1 是为了兼容,不考虑 }function vuexInit () { const options = this.$options // 这里的 this 代表每一个组件,具有 beforeCreate 钩子// store injection if (options.store) { // this.$options.store存在 // 1. 说明是根组件,即通过 const vue = new Vue({store: store}) 生成的实例vue,即根组件this.$store = typeof options.store === 'function' ? options.store() : options.store // 在根组件上改在 $store 属性 // 1. store是个函数,直接调用函数,返回值赋值 // 2. store是个对象,就直接赋值 } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store // 如果不是根组件,并且父组件存在 // 1. 因为:( 父组件的beforeCreate ) 会早于 ( 子组件的beforeCreate ) 先执行,而除了根组件,其余组件的 $store 都是使用父组件的 $store // 2. 所以:导致所有组件的 $store 都是使用的 根组件的 $store,根组件只用自身的 $store,即一层层传递 // 3. 最终:所有组件都使用了 根组件的$store , 即都使用了传入的 store 实例 } } }

(3) Store类的constructor this._modules = new ModuleCollection(options)
  • ModuleCollection 类
    • 主要作用
        1. 赋值 this.root 为根模块
        1. 如果模块中存在 modules 属性,就给父模块的 _children 属性对象中添加该模块的对应关系
    • 实例属性
      • root
        • 即new Module实例
    • 原型属性
      • getNamespace:如果module的namespaced属性存在,就递归的拼接path
    • 在构造函数中调用 this.register([], rawRootModule, false)
    • this.register([], rawRootModule, false)
      • 主要作用就是:
      • new Module(rawModule, runtime)
        • 实例属性
          • 实例上挂载 runtime, _children, _rawModule, state
          • _children:默认是一个空对象
            • 用来存放子module
          • _rawModule:就是传入的module
            • 要么是根module即传入Vuex的options
            • 要么是modules中的各个module
          • state
            • 是一个函数就调用该函数返回state对象
            • 是一个对象直接赋值
        • 原型属性
          • 原型上具有 getChild,addChild,namespaced,update,forEachGetter ...
          • getChild:返回_children对象中的某个module
          • addChild:向_children对象中添加莫格module
          • namespaced:该module是否具有namespaced属性,一个布尔值
          • forEachGetter:循环getters对象,将value和key传给参数函数
      • parent.addChild(path[path.length - 1], newModule)
        • 向父module中的_children对象中添加子模块映射
      • array.prototype.reduce 方法需要注意
        • [].reduce((a,b) => {...}, params)当空数组执行reduce时,不管第一个参数函数执行了任何逻辑,都会直接返回第二个参数params
        • 在源码中有大量的空数组执行reduce的逻辑,比如注册module和注册namespce
      • array.prototype.slice 方法需要注意
        • [].slice(0, -1)返回的是一个空数组
    installModule(this, state, [], this._modules.root)
  • 主要作用
    1. 将module的拼接后的 ( path ) 和 ( moudle) 作为key-value 映射到 ( store._modulesNamespaceMap ) 对象中
    2. state
      • 将所有modules中的module中的state,合并到rootModule的state中
      • key => moduleName
      • value => moduleState
      • 需要注意的是修改state都需要通过 ( store._withCommit ) 包装
    3. local
      • 构造module.context对象,赋值给local对象,将local对象作为参数传入registerMutation,registerAction,registerGetter
      • local对象上有dispatch, commit, getters, state等属性
    4. mutations
      • 将所有的module中的 ( mutations中的函数 ) 都添加到store的 ( this._mutations ) 对象中
        • key:
          • 该module的namespace + mutations中的某个函数名
          • 比如 cart/incrementItemQuantitymodule名 + mutation函数的名字
        • value
          • 一个数组
          • 成员是具体的mutation函数的包装函数,wrappedMutationHandler (payload)
      • 注意:
        • mutations hander

          • 参数
            • 第一个参数 state
              • 该state是当前局部模块的 state,而不是总的state
            • 第二个参数 payload
        • commit

          • store.commit
          • 修改store中state的唯一方式是 store.commit(mutations hander)
          • store.commit('increment', 10)
          • store.commit('increment', {amount: 10})
          • store.commit({type: 'increment',amount: 10})
    5. actions
      • 将所有的module中的 ( actions中的函数 ) 都添加到store的 ( this._actions ) 对象中
      • key:
        • action.root ? key : namespace + key
        • 上面的 key 表示的是actions中的action函数名
        • 上面的 namespace 表示的moudle名+/
        • 比如:cart/addProductToCartmodule名 + action函数的名字
      • value:
        • 一个数组
        • 成员是具体的action函数的包装函数,wrappedActionHandler(payload)
      • 注意:
        • action函数

          • 参数
            • 第一个参数
              • 是一个 context 对象, context 和 store 实例具有相同方法和属性
              • context 对象具有 dispatch commit getters state rootGetters rootState 这些属性
            • 第二个参数
              • payload
        • dispatch

          • store.dispatch
          • action函数是通过 store.dispatch 来触发的
          • store.dispatch('increment')
          • store.dispatch('incrementAsync', {amount: 10})
          • store.dispatch({type: 'incrementAsync', amount: 10})
    6. getters
      • 将所有 module 中的 getters 都映射到 ( store._wrappedGetters ) 对象上
        • key:namespace + key module名/getter函数
        • value: 一个函数,即 wrappedGetter (store)
      • getter函数

        • 参数
          • 第一个参数:局部 state 对象
          • 第二个参数:局部 getters 对象
          • 第三个参数:根 state
          • 第四个参数:根 getters
          • (state, getters, rootState, rootGetters) => {...}
          • 注意 getter 函数的参数是有顺序的,而 action 函数是第一个参数对像没有顺序
    7. 循环moudle中的 ( _children ) 对象,依次执行 ( installModule ) 方法
  • store._modules.getNamespace(path)
    • 主要作用:拼接module的path,然后赋值给 namespace
  • store._withCommit(fn)
    • 包装传入的 mutation 函数
      • 在mutation函数执行时将 this._committing 设置true
      • 在mutation函数执行后将 this._committing 设置false
      • 这样可以保证修改state只能通过mutation函数,直接修改的话没有设置this._committing为true,则证明不是通过mutation在修改
  • Vue.set(parentState, moduleName, module.state)
    • 将moudles中的module中的state合并到父state上
  • makeLocalContext(store, namespace, path)
    • 生成local对象,具有dispatch, commit, getters, state 等属性
  • module.forEachMutation(fn)
    • 循环遍历当前module中的mutations对象,将value和key传入fn(mutation, key)
    • 拼接namespace和key
    • 调用registerMutation(store, namespacedType, mutation, local)
  • registerMutation(store, namespacedType, mutation, local)
    • 向 ( store._mutations[type] ) 数组中push一个 ( mutation包装函数 )
      1. store._mutations[type]
        • typenamespace/getter函数的函数名 ,比如像这样cart/incrementItemQuantity
        • valuewrappedMutationHandler (payload) 这样的函数
      2. 包装函数就是给mutation函数添加一层壳,传入payload,再里面调用 mutation handle 函数
      3. 注意:
        • store._mutations是一个对象
        • 每个 store._mutations[type] 是一个数组
  • module.forEachAction(fn)
    • module.forEachMutation(fn) 类似
    • 循环遍历当前module中的actions对象
      • 用 ( action.root ) 来得到不同的 type
      • 用 ( action.handle ) 是否存在来赋值 handle
      • 调用 registerAction(store, type, handler, local) 函数
  • registerAction(store, type, handler, local)
    • 向 ( store._actions[type] ) 数组中push一个 ( action包装函数 wrappedActionHandler )
  • wrappedActionHandler(payload)
    • 会在内部调用 ( action或者action.handler ) 函数命名为 handler函数
    • 如果 handler 函数的返回值不是 promise,就将返回值转换成 promise对象,并resolve,然后返回fulfilled状态的promise对象
    • handler()
      • 绑定this到store
      • 参数:
        • 第一个参数是一个对象,具有以下属性
          • dispatch
          • commit
          • getters
          • state
          • rootGetters
          • rootState
        • 第二个参数是 payload
  • module.forEachGetter(fn)
    • 循环遍历module中的getters中的所有getter
    • 将value和key传给fn(value, key)
  • registerGetter(store, namespacedType, getter, local)
    • 给 ( store._wrappedGetters ) 添加属性方法
      • key => namespace + key
      • value => wrappedGetter
    • wrappedGetter
      • getter的包装函数,(store) => getter(localState, localGetters, rootState, rootGetters)
      • 参数:局部state getters, 根state getter
commit (_type, _payload, _options)
  • 主要做了以下事情:
    1. 如果第一个参数是对象,就改造参数
      • (一) store.commit('increment', {amount: 10})
      • (二) store.commit({type: 'increment',amount: 10})
      • 将第二种改造成第一种
    2. 寻找该mutaation,即在this._mutations中通过key寻找mutation对应的[mutation]
      • 没找到报错
      • 找到循环数组中存放的mutation包装函数,里面会调用真正的mutation handler函数
    3. 浅拷贝 this._subscribers 然后遍历该数组,调用里面的subscribe函数 => 即更改state后需要响应视图等
  • subscribe
    • subscribe(handler: Function): Function
      • store.subscribe((mutation, state) => {...})
      • 订阅 store 的 mutation
      • handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数
      • 要停止订阅,调用此方法返回的函数即可停止订阅
dispatch (_type, _payload)
  • 主要做了以下事情
    1. 改造参数
    2. 执行this._actionSubscribers中的 before 钩子的action监听函数
      • before 表示订阅处理函数的被调用时机应该在一个 action 分发之前调用
    3. 执行action函数
      • this._actions[type] 数组长度大于1,就promise.all包装,保证result是所有promise都resolve
      • this._actions[type] 数组长度小于等于1,直接调用
    4. 当所有promise都resolve后,即所有异步都返回结果后,执行_actionSubscribers中 after 钩子的action监听函数
      • after 表示订阅处理函数的被调用时机应该在一个 action 分发之后调用
    5. 并将最终的结果resolve出去,返回promise对象
  • subscribeAction
    • subscribeAction(handler: Function): Function
      • store.subscribeAction((action, state) => {...})
      • 订阅 store 的 action
      • handler 会在每个 action 分发的时候调用并接收 action 描述和当前的 store 的 state 这两个参数
      • 注意:
        • 从 3.1.0 起,subscribeAction 也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前)
        • 即可以指定 store.subscribeAction({before: (action, state) => {},after: (action, state) => {}})
源码代码 (1) this._modules = new ModuleCollection(options)
  • 主要作用
    • 赋值 this.root 为根模块
    • 如果模块中存在 modules 属性,就给父模块的 _children 属性对象中添加该模块的对应关系
// 文件目录 src/module/module-collection.jsexport 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) // dev环境,就断言传入的options中的各个属性对象的每个key对于应的value的类型,如果不是应该有的类型,就抛错 }// dev环境并且传入的options符合断言要求 或者 prod环境 // 则进入下面代码const newModule = new Module(rawModule, runtime) // ---------------------------------------- 分析1 // 创建一个 module 实例 // module // 实例属性 runtime, _children, _rawModule, state // 原型属性getChild,addChild,namespaced,update,forEachGetter ...等if (path.length === 0) { this.root = newModule // 数组长度是0,就 new Module实例赋值给 root 属性 } else { // 问题:什么时候path.length !== 0 // 答案:下面会判断是否有 modules 属性,当存在modules属性时,会再次调用register,此时长度不为0const parent = this.get(path.slice(0, -1)) // -------------------------------------------- 分析2 // parent:获取父moduleparent.addChild(path[path.length - 1], newModule) // ------------------------------------- 分析3 // 给父module._children对象中添加key和value,即父modlue的子module }if (rawModule.modules) { // 如果该module中存在modules对象,就遍历modules forEachValue(rawModule.modules, (rawChildModule, key) => { // --------------------------- 分析4 this.register(path.concat(key), rawChildModule, runtime) // path.concat(key) => 类似 [module1],[module2],注意concat不会改变path // rawChildModule=> module1 module2 }) } }

  • 分析1
    // 文件目录 src/module/module.jsexport default class Module { constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // _children 对象 // 用来存放 modules 属性中的所有子moudle // key => moudle的名字 // value => module对象,里面可能有state,mutations,actions,getters等// Store the origin module object which passed by programmer this._rawModule = rawModule // 当前模块const rawState = rawModule.state// Store the origin module's state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state // 函数:就调用,返回stated对象 // 对象:直接赋值 } addChild (key, module) { this._children[key] = module }getChild (key) { return this._children[key] } }

  • 分析2
    get (path) { // 当path是空数组时,[].reducer((a,b) => a.getChild(key), this.root)返回root return path.reduce((module, key) => { return module.getChild(key) }, this.root) }

  • 分析3
    addChild (key, module) { this._children[key] = module // 给module实例的_children属性对象中添加映射 // key => moudle名 // value => module对象 }

  • 分析4
    export function forEachValue (obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)) // 遍历obj // fn(value, key) // 将obj中的 key 作为第二个参数 // 将obj中的 value 作为第一个参数 }

(2) Store类的constructor
Store类的构造函数 - src/stroe.js ---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) // 如果 Vue不存在 并且 window存在 并且 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.`) // 断言 // Vue.use(Vuex)一定要在 new Vuex.store() 前调用 // 是否存在promise,因为vuex依赖promise // Store必须通过new命令调用 }const { plugins = [], // 解构赋值 plugins 和 strict,并赋默认值 strict = false } = options// store internal state this._committing = false // _committing // 标志位,默认是false // 修改state都会用 _withCommit 函数包装,在执行修改state函数中,this._committing=true,修改后置为false // 用来判断是否通过mutation函数修改state,只有通过mutation修改state才是合法的 // 比如在合并state的操作中会用到this._actions = Object.create(null) // _actions // 存放所有moudle的action // { //cart/addProductToCart: [?], f指的是 wrappedActionHandler (payload) //cart/checkout: [?], //products/getAllProducts: [], // }this._actionSubscribers = [] // _actionSubscribers // 收集action监听函数 // 注意区分: // this._subscribers = [] mutation监听函数this._mutations = Object.create(null) // _mutations // 存放所有moudle的mutation // { //cart/incrementItemQuantity: [?], f指的是wrappedMutationHandler //cart/pushProductToCart: [?], //cart/setCartItems: [?], //cart/setCheckoutStatus: [?], //products/decrementProductInventory: [?], //products/setProducts: [?], // }this._wrappedGetters = Object.create(null) // _wrappedGetters // 存放所有module中的getter // { //cart/cartProducts: ? wrappedGetter(store) //cart/cartTotalPrice: ? wrappedGetter(store) // } // 注意:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // 1. this._wrappedGetters 在resetStoreVM(this, state)会用到 // 2. 注意区分 ( store.getters ) 和 ( store._wrappedGetters)this._modules = new ModuleCollection(options) // _modules // ModuleCollection 类主要就是收集所有的moudle // { //root: { //runtime: false, //state: {}, // 注意此时还没有合并state //_children: { // 把moudules中的module放入父模块的 _children 属性中 //cart: Module, //products: Module, //}, //_rawModule: 根module //} // }this._modulesNamespaceMap = Object.create(null) // _modulesNamespaceMap // namespace 和 mdule 的一一映射对象 // { //cart/: Module //products/: Module // }this._subscribers = [] // _subscribers // mutation监听函数 // 注意区分: // this._actionSubscribers = [] action监听函数this._watcherVM = new Vue() this._makeLocalGettersCache = Object.create(null)// 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) // 绑定dispatch函数的this到store实例上 } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) // 绑定commit函数的this到store实例上 }// strict mode this.strict = strict // 严格模式 // 默认是 flase // 严格模式下,只能通过mutation修改state // 在生产环境中建议关闭,因为严格模式会深度监测状态树来检测不合规的状态变更,有性能损耗const state = this._modules.root.state // 根state// init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root)// --------------------------------- 下面会分析// initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) // ----------------------------------------------------------- 下面会分析// apply plugins plugins.forEach(plugin => plugin(this)) // 循环遍历插件,传入stroeconst useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools // 是否存在:传入 new Vuex.stroe(options)中的options中存在devtools // 存在:options.devtools // 不存在:Vue.config.devtoolsif (useDevtools) { devtoolPlugin(this) } } }

(3) installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) { const isRoot = !path.length // 当path数组长度是0,则是根moduleconst namespace = store._modules.getNamespace(path) // 获取 namespace => module名 + / //=> 或者 ''// register in namespace map if (module.namespaced) { // module.namespaced // 每个module可以有namespaced属性,是一个布尔值 // 表示开启局部module命名 // 命名空间官网介绍 (https://vuex.vuejs.org/zh/guide/modules.html)if (store._modulesNamespaceMap[namespace] && __DEV__) { console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`) // 重复了 } store._modulesNamespaceMap[namespace] = module // _modulesNamespaceMap // 建立 module 和 nameSpace 的映射关系 // key : namespace // vlaue: module // { //cart/: Module //products/: Module // } }// set state if (!isRoot && !hot) { // 不是根模块 并且 hot为flase,才会进入判断const parentState = getNestedState(rootState, path.slice(0, -1)) // parentState // 获取该模块的 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) // 合并所有modules中的state到rootState }) }const local = module.context = makeLocalContext(store, namespace, path) // 声明 module.context 并赋值 // local // dispatch // commit // getters // statemodule.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) // 把所有modules中的每一个mutations中的的mutation函数添加到 _mutations 对象上 // _mutations 对象如下的格式 // { //cart/incrementItemQuantity: [?], f指的是wrappedMutationHandler //cart/pushProductToCart: [?], //cart/setCartItems: [?], //cart/setCheckoutStatus: [?], //products/setProducts: [f], // } })module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) // 把所有module中的每一个actions中的的action函数添加到 _actions 对象上 // _actions 对象如下的格式 // { //cart/addProductToCart: [?], f指的是 wrappedActionHandler (payload) //cart/checkout: [?] //products/getAllProducts: [] // } })module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) // 把所有modul中的每一个getter函数添加到 _wrappedGetters 对象上 // 存放所有module中的getter // { //cart/cartProducts: ? wrappedGetter(store) //cart/cartTotalPrice: ? wrappedGetter(store) // } })module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) // 循环遍历module._children对象,并在每次循环中执行 installModule 方法 }) }

(4) resetStoreVM(this, this.state, hot)
function resetStoreVM (store, state, hot) { // resetStoreVM // 参数 // store // state // hotconst oldVm = store._vm // oldVm 缓存旧的store._vm// bind store public getters store.getters = {} // 在 store 实例上添加 getters 属性对象,初始值是一个空对象 // 注意: // 1. 区分 store._wrappedGetters 和 store.getters// reset local getters cache store._makeLocalGettersCache = Object.create(null)const wrappedGetters = store._wrappedGetters // wrappedGetters // 缓存 store._wrappedGettersconst computed = {} //声明computed变量forEachValue(wrappedGetters, (fn, key) => { // 循环wrappedGetters,将 value 和 key 作为参数传入forEachValue的第二个参数函数computed[key] = partial(fn, store) // 1. partial是这样一个函数 // function partial (fn, arg) { //return function () { //return fn(arg) //} // } // 2. 即 computed[key] = () => fn(store) // fn 就是具体的getter函数Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // 可枚举 }) // 1. 给 store.getters 对象添加 key 属性 // 2. 访问 stroe.getters[key] = store._vm[key] // 即访问 store.getter.aaaa 相当于访问 store._vm.aaaa })// 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.silentVue.config.silent = true //开启取消警告 // 取消 Vue 所有的日志与警告,即在new Vue()时不会有警告store._vm = new Vue({ data: { ?state: state // 11. 将传入的state赋值给data中的 ?state 属性 }, computed // 22. 将computed变狼赋值给Vue中的computed属性 (computed[key] = () => fn(store)) }) // store._vm = new Vue(...) // 经过上面 1122 使得 state和() => fn(store) 具有响应式Vue.config.silent = silent // 关闭取消警告// enable strict mode for new vm if (store.strict) { enableStrictMode(store) // 使能严格模式,保证修改store只能通过mutation }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()) // dom更新后,摧毁vue实例 // const oldVm = store._vm // store._vm = new Vue() } }

(5) commit
commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) // 构造commit需要的参数类型 // 1. store.commit('increment', 10) // 2. store.commit('increment', {amount: 10}) // 3. store.commit({type: 'increment',amount: 10}) // 就是将第 3 种情况构造成第 2 种的情况const mutation = { type, payload }const entry = this._mutations[type] // entry 找到需要提交的mutation函数组成的数组,是一个数组,数组种就是包装过后的mutation handle函数if (!entry) { // 没找到该mutation if (__DEV__) { console.error(`[vuex] unknown mutation type: ${type}`) } return }this._withCommit(() => { // this._withCommit 保证修改state是合法手段,即 this._committing在修改是是true entry.forEach(function commitIterator (handler) { handler(payload) // 传入参数执行mutation handle 函数 }) })this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .forEach(sub => sub(mutation, this.state)) // 浅拷贝 this._subscribers 然后遍历该数组,调用里面的subscribe函数 // 即更改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' ) } }

(6) dispatch
dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) // 构造commit需要的参数类型 // 1. store.dispatch('increment') // 2. store.dispatch('incrementAsync', {amount: 10}) // 3. store.dispatch({type: 'incrementAsync', amount: 10}) // 就是将第 3 种情况构造成第 2 种的情况const action = { type, payload } const entry = this._actions[type] if (!entry) { // 没找到该action if (__DEV__) { console.error(`[vuex] unknown action type: ${type}`) } return }try { 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)) // before钩子action监听函数 // before 表示订阅处理函数的被调用时机应该在一个 action 分发之前调用 } 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) // 长度大于1,promise.all()保证result所有resolve // 长度小于等于1,直接调用return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) // after 表示订阅处理函数的被调用时机应该在一个 action 分发之后调用 } catch (e) { if (__DEV__) { console.warn(`[vuex] error in after action subscribers: `) console.error(e) } } resolve(res) // resolve最终结果 }, error => { try { this._actionSubscribers .filter(sub => sub.error) .forEach(sub => sub.error(action, this.state, error)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in error action subscribers: `) console.error(e) } } reject(error) }) }) }

(7) mapstate
  • 首先是 mapstate如何使用
    官网的例子computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }) }当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])

  • 源码
    • 把state构造成computed对象返回
    • 根据namespace把 ( 局部的state和getter作为参数 ) 传给传入的参数对象的 ( 属性函数 )
    export const mapState = normalizeNamespace((namespace, states) => { // normalizeNamespace // 返回改装参数后的,f(namespace, states) // 改成下面的参数形式 // ...mapState('some/nested/module', { //a: state => state.a, //b: state => state.b // }) const res = {} if (__DEV__ && !isValidMap(states)) { // 如果是dev环境 并且 states 不是是一个对象或者一个数组 // 报错// function isValidMap (map) { //return Array.isArray(map) || isObject(map) // } console.error('[vuex] mapState: mapper parameter must be either an Array or an Object') }normalizeMap(states).forEach(({ key, val }) => { // 1. 如果states是数组,返回一个数组,每个成员是一个对象({ key: key, val: key }) // 2. 如果states是对象,返回一个数组,每个成员是一个对象({ key:key, val: map[key] }) res[key] = function mappedState () { let state = this.$store.state let getters = this.$store.getters if (namespace) { const module = getModuleByNamespace(this.$store, 'mapState', namespace) // module // 在 ( store._modulesNamespaceMap ) 对象中找到 ( 参数namespace ) 对应的 ( module )if (!module) { return }state = module.context.state // 获取该module种的局部state getters = module.context.getters // 获取该module种的局部getters } return typeof val === 'function' ? val.call(this, state, getters) : state[val] // val是一个函数,就调用函数val.call(this, state, getters)返回 // val不是函数,就直接返回 state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res // 最后返回res对象 // res对象会作为组件的 computed })

------function normalizeNamespace (fn) { return (namespace, map) => { if (typeof namespace !== 'string') { // 如果 namespace 不是一个字符串 // 说明传入只传入了一个参数 // 就把namespace='',把第一个不是字符串的参数赋值给第二个参数 map = namespace namespace = '' } else if (namespace.charAt(namespace.length - 1) !== '/') { namespace += '/' // 没有 / 则添加 } return fn(namespace, map) // 返回转换参数过后的 fn } }

------function getModuleByNamespace (store, helper, namespace) { // 1. getModuleByNamespace(this.$store, 'mapState', namespace)const module = store._modulesNamespaceMap[namespace] // 找到namespace对应的module if (__DEV__ && !module) { console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`) } return module // 返回module }

------function normalizeMap (map) { if (!isValidMap(map)) { return [] // 不是数组或者对象,默认返回一个数组 } return Array.isArray(map) ? map.map(key => ({ key, val: key })) : Object.keys(map).map(key => ({ key, val: map[key] })) // 1. 如果是数组,返回一个数组,每个成员是一个对象({ key: key, val: key }) // 2. 如果是对象,返回一个数组,每个成员是一个对象({ key:key, val: map[key] }) }

使用中的注意点 mapState - (带namespace和不带namespace)
mapGetters
mapMutations
mapActions
  • ui组件中

  • store/vuexModule
    import {getName} from '../../api/home'const vuex = { namespaced: true, state() { return { count: 2, name: 'init name' } }, getters: { square(state, getters, rootState, rootGetters) { console.log(state, 'state') console.log(getters, 'getters') console.log(rootState, 'rootState') console.log(rootGetters, 'rootGetters') return (state.count * rootState.rootCount) } }, mutations: { AddCount(state, payload) { state.count = state.count + payload }, getNameData(state, payload) { state.name = payload } }, actions: { async getData(context) { // dispatch不穿参给action函数 const {commit} = context const res = await getName({ url: "/home", method: "get" }); if (res && res.data && res.data.name) { console.log(res.data.name, '2222') commit('getNameData', res.data.name) } }, async getData2(context, payload) { // dispatch穿参给action函数 // action(context, payload) // 第一个参数: context和store具有相同的api // 第二个参数:payload是dispatch一个action穿过来的参数 const {commit} = context const res = await getName(payload); // const res = await getName({ //url: "/home", //method: "get" // }); if (res && res.data && res.data.name) { console.log(res.data.name, '2222') commit('getNameData', res.data.name) } }, } }; export default vuex

资料 vuex官网文档 https://vuex.vuejs.org/zh/
【[源码] vuex】川神 较全面 https://juejin.im/post/684490...
2.3.1版本Vuex,过程详细:https://juejin.im/post/684490...
yck https://juejin.im/post/684490...

    推荐阅读