[源码] vuex

2021-09-24  本文已影响0人  woow_wu7

导航

[深入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 如何编写一个插件

插件:

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() 源码
// 文件路径: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 如何安装和使用

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)

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>

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

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

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

源码代码

(1) this._modules = new ModuleCollection(options)

// 文件目录 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
      })
    }
  }
// 文件目录 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]
  }
}
  get (path) {
    // 当path是空数组时,[].reducer((a,b) => a.getChild(key), this.root)返回root
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }
addChild (key, module) {
    this._children[key] = module
    // 给module实例的_children属性对象中添加映射
    // key => moudle名
    // value => module对象
}
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

官网的例子

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])
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

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

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

上一篇下一篇

猜你喜欢

热点阅读