美文网首页
[源码] vuex

[源码] vuex

作者: woow_wu7 | 来源:发表于2021-09-24 08:41 被阅读0次

    导航

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承
    [深入04] 事件循环
    [深入05] 柯里化 偏函数 函数记忆
    [深入06] 隐式转换 和 运算符
    [深入07] 浏览器缓存机制(http缓存机制)
    [深入08] 前端安全
    [深入09] 深浅拷贝
    [深入10] Debounce Throttle
    [深入11] 前端路由
    [深入12] 前端模块化
    [深入13] 观察者模式 发布订阅模式 双向数据绑定
    [深入14] canvas
    [深入15] webSocket
    [深入16] webpack
    [深入17] http 和 https
    [深入18] CSS-interview
    [深入19] 手写Promise
    [深入20] 手写函数

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex
    [源码-vue01] data响应式 和 初始化渲染
    [源码-vue02] computed 响应式 - 初始化,访问,更新过程
    [源码-vue03] watch 侦听属性 - 初始化和更新
    [源码-vue04] Vue.set 和 vm.$set
    [源码-vue05] Vue.extend

    [源码-vue06] Vue.nextTick 和 vm.$nextTick

    前置知识

    一些单词

    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)
    • 注意点:

      • <font color=red>每个插件都应该暴露一个 install 方法,并且install方法的第一个参数必须是 Vue 构造器,第二个参数是一个可选的参数对象 </font>
        • 因为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 = false
    
    Vue.prototype.$axios = axios
    
    Vue.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() 调用时自动执行
    • 注意:
      • <font color=red>Vue.use() 需要在 new Vue() 之前被调用</font>
      • 当 install 方法被同一个插件多次调用,插件将只会被安装一次
    • Vue.use() 源码
      • 主要做了以下事情
        1. 如果插件注册过
          • 直接返回 ( 返回this主要为了能链式调用 )
        2. 没注册过
          • 判断该插件是对象还是函数
            • <font color=red>对象:调用install方法</font>
            • <font color=red>函数:把该插件作为install方法,直接调用该函数</font>
          • 把该插件添加进插件数组,下次判断是否注册过时,就注册过了
          • 返回 this 方便链式调用
        • 总结:
          • Vue.use() 最主要就是调用插件的 install 方法
    // Vue.use() 源码
    // 文件路径:core/global-api/use.js
    
    
    import { 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<any> {
      start = start || 0
      let i = list.length - start
      const ret: Array<any> = 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 方法
      • <font color=red>Vuex.install</font> 方法会调用 <font color=red>applyMixin(Vue)</font>
        • applyMixin(Vue)
          • 主要就是把store实例子注入每个组件中
          • 具体就是在组件的的 beforeCreate 生命周期中mixin混入 <font color=red>vuexInit</font> 方法,从而把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

    <font color=blue size=5>this._modules = new ModuleCollection(options)</font>

    • ModuleCollection 类
      • 主要作用
          1. 赋值 this.root 为根模块
          1. 如果模块中存在 modules 属性,就给父模块的 _children 属性对象中添加该模块的对应关系
      • 实例属性
        • root
          • 即new Module实例
      • 原型属性
        • getNamespace:如果module的namespaced属性存在,就递归的拼接path
      • 在构造函数中调用 this.register([], rawRootModule, false)
      • <font color=blue>this.register([], rawRootModule, false)</font>
        • 主要作用就是:
        • <font color=blue>new Module(rawModule, runtime)</font>
          • 实例属性
            • 实例上挂载 <font color=blue>runtime, _children, _rawModule, state</font>
            • _children:默认是一个空对象
              • 用来存放子module
            • _rawModule:就是传入的module
              • 要么是根module即传入Vuex的options
              • 要么是modules中的各个module
            • state
              • 是一个函数就调用该函数返回state对象
              • 是一个对象直接赋值
          • 原型属性
            • 原型上具有 <font color=blue>getChild,addChild,namespaced,update,forEachGetter ...</font>
            • getChild:返回_children对象中的某个module
            • addChild:向_children对象中添加莫格module
            • namespaced:该module是否具有namespaced属性,一个布尔值
            • forEachGetter:循环getters对象,将value和key传给参数函数
        • <font color=blue>parent.addChild(path[path.length - 1], newModule)</font>
          • 向父module中的_children对象中添加子模块映射
        • <font color=blue>array.prototype.reduce 方法需要注意</font>
          • [].reduce((a,b) => {...}, params)当空数组执行reduce时,不管第一个参数函数执行了任何逻辑,都会直接返回第二个参数params
          • 在源码中有大量的空数组执行reduce的逻辑,比如注册module和注册namespce
        • <font color=blue>array.prototype.slice 方法需要注意</font>
          • [].slice(0, -1)返回的是一个空数组

    <font color=red size=5>installModule(this, state, [], this._modules.root)</font>

    • 主要作用
      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)
        • 注意:
          • <table><tr><td bgcolor=orange>mutations hander</td></tr></table>
            • 参数
              • 第一个参数 state
                • 该state是当前局部模块的 state,而不是总的state
              • 第二个参数 payload
          • <table><tr><td bgcolor=yellow>commit</td></tr></table>
            • 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)
        • 注意:
          • <table><tr><td bgcolor=orange>action函数 </td></tr></table>
            • 参数
              • 第一个参数
                • 是一个 context 对象, context 和 store 实例具有相同方法和属性
                • context 对象具有 dispatch commit getters state rootGetters rootState 这些属性
              • 第二个参数
                • payload
          • <table><tr><td bgcolor=yellow>dispatch</td></tr></table>
            • 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)
        • <table><tr><td bgcolor=orange>getter函数</td></tr></table>
          • 参数
            • 第一个参数:局部 state 对象
            • 第二个参数:局部 getters 对象
            • 第三个参数:根 state
            • 第四个参数:根 getters
            • (state, getters, rootState, rootGetters) => {...}
            • 注意 getter 函数的参数是有顺序的,而 action 函数是第一个参数对像没有顺序
      7. 循环moudle中的 ( _children ) 对象,依次执行 ( installModule ) 方法
    • <font color=red>store._modules.getNamespace(path)</font>
      • 主要作用:拼接module的path,然后赋值给 namespace
    • <font color=red>store._withCommit(fn)</font>
      • 包装传入的 mutation 函数
        • 在mutation函数执行时将 this._committing 设置true
        • 在mutation函数执行后将 this._committing 设置false
        • 这样可以保证修改state只能通过mutation函数,直接修改的话没有设置this._committing为true,则证明不是通过mutation在修改
    • <font color=red>Vue.set(parentState, moduleName, module.state)</font>
      • 将moudles中的module中的state合并到父state上
    • <font color=red>makeLocalContext(store, namespace, path)</font>
      • 生成local对象,具有dispatch, commit, getters, state 等属性
    • <font color=red>module.forEachMutation(fn)</font>

      • 循环遍历当前module中的mutations对象,将value和key传入fn(mutation, key)
      • 拼接namespace和key
      • 调用registerMutation(store, namespacedType, mutation, local)
    • <font color=red>registerMutation(store, namespacedType, mutation, local)</font>

      • 向 ( 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] 是一个数组
    • <font color=red>module.forEachAction(fn)</font>

      • module.forEachMutation(fn) 类似
      • 循环遍历当前module中的actions对象
        • 用 ( action.root ) 来得到不同的 type
        • 用 ( action.handle ) 是否存在来赋值 handle
        • 调用 registerAction(store, type, handler, local) 函数
    • <font color=red>registerAction(store, type, handler, local)</font>

      • 向 ( store._actions[type] ) 数组中push一个 ( action包装函数 wrappedActionHandler )
    • <font color=red>wrappedActionHandler(payload)</font>

      • 会在内部调用 ( action或者action.handler ) 函数命名为 handler函数
      • 如果 handler 函数的返回值不是 promise,就将返回值转换成 promise对象,并resolve,然后返回fulfilled状态的promise对象
      • <font color=red>handler()</font>
        • 绑定this到store
        • 参数:
          • 第一个参数是一个对象,具有以下属性
            • dispatch
            • commit
            • getters
            • state
            • rootGetters
            • rootState
          • 第二个参数是 payload
    • <font color=red>module.forEachGetter(fn)</font>

      • 循环遍历module中的getters中的所有getter
      • 将value和key传给fn(value, key)
    • <font color=red>registerGetter(store, namespacedType, getter, local)</font>

      • 给 ( store._wrappedGetters ) 添加属性方法
        • key => namespace + key
        • value => wrappedGetter
      • <font color=red>wrappedGetter</font>
        • getter的包装函数,(store) => getter(localState, localGetters, rootState, rootGetters)
        • 参数:局部state getters, 根state getter

    <font color=DarkOrchid>commit (_type, _payload, _options) </font>

    • 主要做了以下事情:
      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 后的状态作为参数
        • 要停止订阅,调用此方法返回的函数即可停止订阅

    <font color=DarkOrchid>dispatch (_type, _payload) </font>

    • 主要做了以下事情
      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.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)
          // 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,此时长度不为0
    
          const parent = this.get(path.slice(0, -1)) // -------------------------------------------- 分析2
          // parent:获取父module
    
          parent.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.js
    
    export 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))
        // 循环遍历插件,传入stroe
    
        const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
        // 是否存在:传入 new Vuex.stroe(options)中的options中存在devtools
        // 存在:options.devtools
        // 不存在:Vue.config.devtools
    
        if (useDevtools) {
          devtoolPlugin(this)
        }
      }
    }
    

    (3) installModule(this, state, [], this._modules.root)

    function installModule (store, rootState, path, module, hot) {
      const isRoot = !path.length
      // 当path数组长度是0,则是根module
    
      const 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
        // state
    
      module.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
          // hot
    
      const 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._wrappedGetters
    
      const 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.silent
    
      Vue.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组件中
    <template>
      <div class="vuex">
        <div>
          <div style="font-size: 30px">vuex</div>
          <div>dispatch一个action => store.dispatch({type: 'actionName', payload: ''})</div>
          <div>commit一个mutation => store.dispatch({type: 'actionName', payload: ''})</div>
         
          <div>------</div>
          <button @click="changeCount">点击修改vuexModule中的count+1 </button>
          <div>{{moduleState.count}}</div>
    
          <div>------</div>
          <div><button @click="getName">点击,发起请求,获取name数据,利用Vuex actions - 不传参数</button></div>
          <div><button @click="getName2">点击,发起请求,获取name数据,利用Vuex actions - 传参</button></div>
          <div>{{moduleState.name}}</div>
        </div>
      </div>
    </template>
    
    <script>
    import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
    
    export default {
      name: "vuex",
      data() {
        return {};
      },
      computed: {
        ...mapState({
          rootState: state => {   // --------------- 命名为 rootState
            return state; // ----------------------- 这里的state是rootMoudule的state
          }
        }),
        ...mapState("vuexModule", { // ------------ namespace
          moduleState: state => { // -------------- 命名为 moduleState
            return state; // ---------------------- 这里的state是moudles中vuexModule的state
          }
        }),
        ...mapGetters("vuexModule", { // ---------- 第二个参数是对象,即可以修改getter的名字
          changeGetterName: "square"
        }),
        ...mapGetters("vuexModule", ['square']), // 第二个参数是数组
      },
      methods: {
        ...mapMutations('vuexModule', { 
          addCountChangeName: 'AddCount' // ------- 对象方式,可以修改mutation的名字
        }),
        ...mapActions('vuexModule', ['getData', 'getData2']), // mapActions
        changeCount() {
          this.addCountChangeName(1) // ----------- 参数将作为mutation(state, payload)的payload
        },
        getName() {
          this.getData() // ----------------------- 不传参给action函数,处理异步
        },
        getName2() {
          this.getData2({ // ----------------------- 传参给action函数,处理异步
            url: "/home",
            method: "get"
          })
        }
      },
      mounted() {
        console.log(
          this.rootState.vuexModule.count,
          "没有namespace的mapState获取的state - 访问coount"
        );
        console.log(
          this.moduleState.count,
          "具有namespace的mapState获取的state - 访问count"
        );
        console.log(this.changeGetterName, 'mapGetters第二个参数是对象');
        console.log(this.square, 'mapGetters第二个参数是数组');
      }
    };
    </script>
    
    
    • 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/

    川神 较全面 https://juejin.im/post/6844904001192853511
    2.3.1版本Vuex,过程详细:https://juejin.im/post/6844903495263322119
    yck https://juejin.im/post/6844903676721496071

    相关文章

      网友评论

          本文标题:[源码] vuex

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