[源碼] vuex

導(dǎo)航

[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運(yùn)算符
[深入07] 瀏覽器緩存機(jī)制(http緩存機(jī)制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue項(xiàng)目
[部署03] gitlab-CI

[源碼-webpack01-前置知識(shí)] AST抽象語(yǔ)法樹
[源碼-webpack02-前置知識(shí)] Tapable
[源碼-webpack03] 手寫webpack - compiler簡(jiǎn)單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應(yīng)式 和 初始化渲染
[源碼-vue02] computed 響應(yīng)式 - 初始化雁比,訪問(wèn),更新過(guò)程
[源碼-vue03] watch 偵聽屬性 - 初始化和更新
[源碼-vue04] Vue.set 和 vm.$set
[源碼-vue05] Vue.extend

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

前置知識(shí)

一些單詞

Mutation:變異
raw:未加工
assert: 斷言

internal:內(nèi)部的
( store internal state : store內(nèi)部的state )

duplicate:重復(fù),賦值勉抓,副本
localized:局部的

unify:統(tǒng)一
( unifyObjectStyle 統(tǒng)一對(duì)象類型 )

Valid: 有效的

vue 如何編寫一個(gè)插件

  • 插件通常來(lái)為 vue 添加 ( 全局功能 )

    • 全局方法绢慢, 全局資源捡偏, 全局混入豺憔, vue實(shí)例方法 等
    • Vue構(gòu)造器靜態(tài)方法和屬性伦糯,Vue.prototype實(shí)例方法和屬性mixin靠娱,directive
  • 如果使用插件

    • 通過(guò)組件形式:
        1. Vue.use(plugin, [options])
        1. new Vue(組件選項(xiàng))
    • 通過(guò)script方式引入
      • 比如 vue-router插件
        • 會(huì)在 index.js 內(nèi)部判斷 如果是 ( 瀏覽器環(huán)境并且window.Vue存在 )沧烈,就自動(dòng)調(diào)用 window.Vue.use(VueRouter)
  • 注意點(diǎn):

    • <font color=red>每個(gè)插件都應(yīng)該暴露一個(gè) install 方法,并且install方法的第一個(gè)參數(shù)必須是 Vue 構(gòu)造器像云,第二個(gè)參數(shù)是一個(gè)可選的參數(shù)對(duì)象 </font>
      • 因?yàn)閕nstall方法的第一個(gè)參數(shù)是 Vue 構(gòu)造器锌雀,所以可以獲取Vue上的directive, mixin, 等等
    • 后面會(huì)知道如果沒(méi)有暴露install方法,那插件本身就必須是一個(gè)函數(shù)迅诬,該函數(shù)就會(huì)被當(dāng)做install方法
  • 代碼

插件:

const firstPlugin = {
    install(Vue, options) {
        console.log(options, 'options')

        // 全局方法 firstGlobalMethod
        Vue.firstGlobalMethod = function () {
            console.log('插件 - 全局方法 - 即Vue構(gòu)造器的靜態(tài)方法')
        }

        // 添加全局資源 全局指令
        Vue.directive('first-directive', {
            bind(el, binding, vnode, oldVnode) {
                console.log('只調(diào)用一次腋逆,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置')
                console.log(el, 'el 指令所綁定的元素侈贷,可以用來(lái)直接操作DOM')
                console.log(binding, 'binding')
                console.log(vnode, 'Vue 編譯生成的虛擬節(jié)點(diǎn)')
                console.log(oldVnode, '上一個(gè)虛擬節(jié)點(diǎn)惩歉,僅在 update 和 componentUpdated 鉤子中可用')
            },
            inserted() {
                console.log('被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)')
            }
        })

        // 全局混合 注入組件選項(xiàng)
        Vue.mixin({
            created: function () {
                console.log('插件 - 全局混合 - 在所有組件中混入created()生命周期')
            }
        })

        // 實(shí)例方法
        // 一般規(guī)定在Vue.prototype上掛載的屬性和方法都要以 $ 開頭
        Vue.prototype.$firstPluginMethod = function () {
            console.log('插件 - 實(shí)例方法 - 掛載在vue實(shí)例原型對(duì)象上的方法俏蛮,記得該屬性和方法以 $ 開頭')
        }
    }
}

export default firstPlugin
注冊(cè)插件:

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) // -------------------------------------------------------------- 注冊(cè)插件

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Vue.use()

  • Vue.use( plugin )
  • 參數(shù):object 或者 function
  • 作用:
    • 安裝Vue插件
    • 插件是對(duì)象:需提供 install 方法
    • 插件是函數(shù):自身會(huì)作為 install 方法
    • install方法:會(huì)將 Vue 作為參數(shù)撑蚌,在 Vue.use() 調(diào)用時(shí)自動(dòng)執(zhí)行
  • 注意:
    • <font color=red>Vue.use() 需要在 new Vue() 之前被調(diào)用</font>
    • 當(dāng) install 方法被同一個(gè)插件多次調(diào)用,插件將只會(huì)被安裝一次
  • Vue.use() 源碼
    • 主要做了以下事情
      1. 如果插件注冊(cè)過(guò)
        • 直接返回 ( 返回this主要為了能鏈?zhǔn)秸{(diào)用 )
      2. 沒(méi)注冊(cè)過(guò)
        • 判斷該插件是對(duì)象還是函數(shù)
          • <font color=red>對(duì)象:調(diào)用install方法</font>
          • <font color=red>函數(shù):把該插件作為install方法搏屑,直接調(diào)用該函數(shù)</font>
        • 把該插件添加進(jìn)插件數(shù)組争涌,下次判斷是否注冊(cè)過(guò)時(shí),就注冊(cè)過(guò)了
        • 返回 this 方便鏈?zhǔn)秸{(diào)用
      • 總結(jié):
        • Vue.use() 最主要就是調(diào)用插件的 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構(gòu)造器上掛載use方法

    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // installedPlugins
    // 如果 Vue._installedPlugins 存在就直接賦值
    // 如果 Vue._installedPlugins 不存在賦值為空數(shù)組

    if (installedPlugins.indexOf(plugin) > -1) {
      // 該插件在插件數(shù)組installedPlugins中存在就直接返回
      // 這里返回 this睬棚,就可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用第煮,這里的this就是Vue
      return this
    }

    const args = toArray(arguments, 1)
    // additional parameters
    // 將除去Vue以外的參數(shù)收集成數(shù)組

    args.unshift(this)
    // 將Vue添加到數(shù)組的最前面
    // 因?yàn)閍rgs要作為插件install方法的參數(shù),而install方法規(guī)定第一個(gè)參數(shù)必須是Vue構(gòu)造器 

    if (typeof plugin.install === 'function') {
      // 插件是對(duì)象抑党,就把插件對(duì)象的install方法的this綁定到該plugin上包警,傳入?yún)?shù)執(zhí)行
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // 插件是函數(shù),傳參執(zhí)行
      plugin.apply(null, args)
    }

    installedPlugins.push(plugin)
    // 如果該插件在installedPlugins不存在底靠,首先在上面賦值為空數(shù)組害晦,執(zhí)行isntall后,把該插件添加進(jìn)installedPlugins表示存在

    return this
    // return this 是為了鏈?zhǔn)秸{(diào)用暑中,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) // -------------------------------- 會(huì)調(diào)用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) 會(huì)調(diào)用Vuex中的 Vuex.install 方法
    • <font color=red>Vuex.install</font> 方法會(huì)調(diào)用 <font color=red>applyMixin(Vue)</font>
      • applyMixin(Vue)
        • 主要就是把store實(shí)例子注入每個(gè)組件中
        • 具體就是在組件的的 beforeCreate 生命周期中mixin混入 <font color=red>vuexInit</font> 方法壹瘟,從而把store實(shí)例注入每個(gè)組件
          • mixin需要注意:
              1. 同名鉤子函數(shù)將合并為一個(gè)數(shù)組,因此都將被調(diào)用
              1. 混入對(duì)象的鉤子將在組件自身鉤子之前調(diào)用
  • 總結(jié):
    • Vue.use(Vuex) => Vuex.install() => applyMixin(Vue) => Vue.mixin({ beforeCreate: vuexInit })
    • 通過(guò)Vue.use(Vuex)的最終效果就是將store實(shí)例注入到每個(gè)組件中鳄逾,且是共用同一個(gè)store實(shí)例
    • 所以:可以在每個(gè)組件中通過(guò) this.$store 訪問(wèn)到 strore 實(shí)例
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相等,并且是生產(chǎn)環(huán)境雕凹,返回
  }
  Vue = _Vue
  // Vue不存在殴俱,就賦值傳入的參數(shù)_Vue
  applyMixin(Vue)
  // 調(diào)用 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 代表每一個(gè)組件线欲,具有 beforeCreate 鉤子
  
    // store injection
    if (options.store) {
      // this.$options.store存在
        // 1. 說(shuō)明是根組件明场,即通過(guò) const vue = new Vue({store: store}) 生成的實(shí)例vue,即根組件
  
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
      // 在根組件上改在 $store 屬性
        // 1. store是個(gè)函數(shù)李丰,直接調(diào)用函數(shù)苦锨,返回值賦值
        // 2. store是個(gè)對(duì)象,就直接賦值
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
      // 如果不是根組件趴泌,并且父組件存在
        // 1. 因?yàn)椋? 父組件的beforeCreate ) 會(huì)早于 ( 子組件的beforeCreate ) 先執(zhí)行舟舒,而除了根組件,其余組件的 $store 都是使用父組件的 $store
        // 2. 所以:導(dǎo)致所有組件的 $store 都是使用的 根組件的 $store踱讨,根組件只用自身的 $store魏蔗,即一層層傳遞
        // 3. 最終:所有組件都使用了 根組件的$store , 即都使用了傳入的 store 實(shí)例
    }
  }
}

(3) Store類的constructor

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

  • ModuleCollection 類
    • 主要作用
        1. 賦值 this.root 為根模塊
        1. 如果模塊中存在 modules 屬性痹筛,就給父模塊的 _children 屬性對(duì)象中添加該模塊的對(duì)應(yīng)關(guān)系
    • 實(shí)例屬性
      • root
        • 即new Module實(shí)例
    • 原型屬性
      • getNamespace:如果module的namespaced屬性存在,就遞歸的拼接path
    • 在構(gòu)造函數(shù)中調(diào)用 this.register([], rawRootModule, false)
    • <font color=blue>this.register([], rawRootModule, false)</font>
      • 主要作用就是:
      • <font color=blue>new Module(rawModule, runtime)</font>
        • 實(shí)例屬性
          • 實(shí)例上掛載 <font color=blue>runtime, _children, _rawModule, state</font>
          • _children:默認(rèn)是一個(gè)空對(duì)象
            • 用來(lái)存放子module
          • _rawModule:就是傳入的module
            • 要么是根module即傳入Vuex的options
            • 要么是modules中的各個(gè)module
          • state
            • 是一個(gè)函數(shù)就調(diào)用該函數(shù)返回state對(duì)象
            • 是一個(gè)對(duì)象直接賦值
        • 原型屬性
          • 原型上具有 <font color=blue>getChild廓鞠,addChild帚稠,namespaced,update床佳,forEachGetter ...</font>
          • getChild:返回_children對(duì)象中的某個(gè)module
          • addChild:向_children對(duì)象中添加莫格module
          • namespaced:該module是否具有namespaced屬性滋早,一個(gè)布爾值
          • forEachGetter:循環(huán)getters對(duì)象,將value和key傳給參數(shù)函數(shù)
      • <font color=blue>parent.addChild(path[path.length - 1], newModule)</font>
        • 向父module中的_children對(duì)象中添加子模塊映射
      • <font color=blue>array.prototype.reduce 方法需要注意</font>
        • [].reduce((a,b) => {...}, params)當(dāng)空數(shù)組執(zhí)行reduce時(shí)砌们,不管第一個(gè)參數(shù)函數(shù)執(zhí)行了任何邏輯杆麸,都會(huì)直接返回第二個(gè)參數(shù)params
        • 在源碼中有大量的空數(shù)組執(zhí)行reduce的邏輯,比如注冊(cè)module和注冊(cè)namespce
      • <font color=blue>array.prototype.slice 方法需要注意</font>
        • [].slice(0, -1)返回的是一個(gè)空數(shù)組

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

  • 主要作用
    1. 將module的拼接后的 ( path ) 和 ( moudle) 作為key-value 映射到 ( store._modulesNamespaceMap ) 對(duì)象中
    2. state
      • 將所有modules中的module中的state浪感,合并到rootModule的state中
      • key => moduleName
      • value => moduleState
      • 需要注意的是修改state都需要通過(guò) ( store._withCommit ) 包裝
    3. local
      • 構(gòu)造module.context對(duì)象昔头,賦值給local對(duì)象,將local對(duì)象作為參數(shù)傳入registerMutation影兽,registerAction揭斧,registerGetter
      • local對(duì)象上有dispatch, commit, getters, state等屬性
    4. mutations
      • 將所有的module中的 ( mutations中的函數(shù) ) 都添加到store的 ( this._mutations ) 對(duì)象中
        • key:
          • 該module的namespace + mutations中的某個(gè)函數(shù)名
          • 比如 cart/incrementItemQuantitymodule名 + mutation函數(shù)的名字
        • value
          • 一個(gè)數(shù)組
          • 成員是具體的mutation函數(shù)的包裝函數(shù),wrappedMutationHandler (payload)
      • 注意:
        • <table><tr><td bgcolor=orange>mutations hander</td></tr></table>
          • 參數(shù)
            • 第一個(gè)參數(shù) state
              • 該state是當(dāng)前局部模塊的 state峻堰,而不是總的state
            • 第二個(gè)參數(shù) 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中的函數(shù) ) 都添加到store的 ( this._actions ) 對(duì)象中
      • key:
        • action.root ? key : namespace + key
        • 上面的 key 表示的是actions中的action函數(shù)名
        • 上面的 namespace 表示的moudle名+/
        • 比如:cart/addProductToCartmodule名 + action函數(shù)的名字
      • value:
        • 一個(gè)數(shù)組
        • 成員是具體的action函數(shù)的包裝函數(shù)讹开,wrappedActionHandler(payload)
      • 注意:
        • <table><tr><td bgcolor=orange>action函數(shù) </td></tr></table>
          • 參數(shù)
            • 第一個(gè)參數(shù)
              • 是一個(gè) context 對(duì)象, context 和 store 實(shí)例具有相同方法和屬性
              • context 對(duì)象具有 dispatch commit getters state rootGetters rootState 這些屬性
            • 第二個(gè)參數(shù)
              • payload
        • <table><tr><td bgcolor=yellow>dispatch</td></tr></table>
          • store.dispatch
          • action函數(shù)是通過(guò) store.dispatch 來(lái)觸發(fā)的
          • store.dispatch('increment')
          • store.dispatch('incrementAsync', {amount: 10})
          • store.dispatch({type: 'incrementAsync', amount: 10})
    6. getters
      • 將所有 module 中的 getters 都映射到 ( store._wrappedGetters ) 對(duì)象上
        • key:namespace + key module名/getter函數(shù)
        • value: 一個(gè)函數(shù)捐名,即 wrappedGetter (store)
      • <table><tr><td bgcolor=orange>getter函數(shù)</td></tr></table>
        • 參數(shù)
          • 第一個(gè)參數(shù):局部 state 對(duì)象
          • 第二個(gè)參數(shù):局部 getters 對(duì)象
          • 第三個(gè)參數(shù):根 state
          • 第四個(gè)參數(shù):根 getters
          • (state, getters, rootState, rootGetters) => {...}
          • 注意 getter 函數(shù)的參數(shù)是有順序的旦万,而 action 函數(shù)是第一個(gè)參數(shù)對(duì)像沒(méi)有順序
    7. 循環(huán)moudle中的 ( _children ) 對(duì)象,依次執(zhí)行 ( installModule ) 方法
  • <font color=red>store._modules.getNamespace(path)</font>
    • 主要作用:拼接module的path镶蹋,然后賦值給 namespace
  • <font color=red>store._withCommit(fn)</font>
    • 包裝傳入的 mutation 函數(shù)
      • 在mutation函數(shù)執(zhí)行時(shí)將 this._committing 設(shè)置true
      • 在mutation函數(shù)執(zhí)行后將 this._committing 設(shè)置false
      • 這樣可以保證修改state只能通過(guò)mutation函數(shù)成艘,直接修改的話沒(méi)有設(shè)置this._committing為true赏半,則證明不是通過(guò)mutation在修改
  • <font color=red>Vue.set(parentState, moduleName, module.state)</font>
    • 將moudles中的module中的state合并到父state上
  • <font color=red>makeLocalContext(store, namespace, path)</font>
    • 生成local對(duì)象,具有dispatch, commit, getters, state 等屬性
  • <font color=red>module.forEachMutation(fn)</font>

    • 循環(huán)遍歷當(dāng)前module中的mutations對(duì)象狰腌,將value和key傳入fn(mutation, key)
    • 拼接namespace和key
    • 調(diào)用registerMutation(store, namespacedType, mutation, local)
  • <font color=red>registerMutation(store, namespacedType, mutation, local)</font>

    • 向 ( store._mutations[type] ) 數(shù)組中push一個(gè) ( mutation包裝函數(shù) )
      1. store._mutations[type]
        • typenamespace/getter函數(shù)的函數(shù)名 除破,比如像這樣cart/incrementItemQuantity
        • valuewrappedMutationHandler (payload) 這樣的函數(shù)
      2. 包裝函數(shù)就是給mutation函數(shù)添加一層殼,傳入payload琼腔,再里面調(diào)用 mutation handle 函數(shù)
      3. 注意:
        • store._mutations是一個(gè)對(duì)象
        • 每個(gè) store._mutations[type] 是一個(gè)數(shù)組
  • <font color=red>module.forEachAction(fn)</font>

    • module.forEachMutation(fn) 類似
    • 循環(huán)遍歷當(dāng)前module中的actions對(duì)象
      • 用 ( action.root ) 來(lái)得到不同的 type
      • 用 ( action.handle ) 是否存在來(lái)賦值 handle
      • 調(diào)用 registerAction(store, type, handler, local) 函數(shù)
  • <font color=red>registerAction(store, type, handler, local)</font>

    • 向 ( store._actions[type] ) 數(shù)組中push一個(gè) ( action包裝函數(shù) wrappedActionHandler )
  • <font color=red>wrappedActionHandler(payload)</font>

    • 會(huì)在內(nèi)部調(diào)用 ( action或者action.handler ) 函數(shù)命名為 handler函數(shù)
    • 如果 handler 函數(shù)的返回值不是 promise瑰枫,就將返回值轉(zhuǎn)換成 promise對(duì)象,并resolve丹莲,然后返回fulfilled狀態(tài)的promise對(duì)象
    • <font color=red>handler()</font>
      • 綁定this到store
      • 參數(shù):
        • 第一個(gè)參數(shù)是一個(gè)對(duì)象光坝,具有以下屬性
          • dispatch
          • commit
          • getters
          • state
          • rootGetters
          • rootState
        • 第二個(gè)參數(shù)是 payload
  • <font color=red>module.forEachGetter(fn)</font>

    • 循環(huán)遍歷module中的getters中的所有g(shù)etter
    • 將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的包裝函數(shù),(store) => getter(localState, localGetters, rootState, rootGetters)
      • 參數(shù):局部state getters甥材, 根state getter

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

  • 主要做了以下事情:
    1. 如果第一個(gè)參數(shù)是對(duì)象盯另,就改造參數(shù)
      • (一) store.commit('increment', {amount: 10})
      • (二) store.commit({type: 'increment',amount: 10})
      • 將第二種改造成第一種
    2. 尋找該mutaation,即在this._mutations中通過(guò)key尋找mutation對(duì)應(yīng)的[mutation]
      • 沒(méi)找到報(bào)錯(cuò)
      • 找到循環(huán)數(shù)組中存放的mutation包裝函數(shù)洲赵,里面會(huì)調(diào)用真正的mutation handler函數(shù)
    3. 淺拷貝 this._subscribers 然后遍歷該數(shù)組鸳惯,調(diào)用里面的subscribe函數(shù) => 即更改state后需要響應(yīng)視圖等
  • subscribe
    • subscribe(handler: Function): Function
      • store.subscribe((mutation, state) => {...})
      • 訂閱 store 的 mutation
      • handler 會(huì)在每個(gè) mutation 完成后調(diào)用,接收 mutation 和經(jīng)過(guò) mutation 后的狀態(tài)作為參數(shù)
      • 要停止訂閱叠萍,調(diào)用此方法返回的函數(shù)即可停止訂閱

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

  • 主要做了以下事情
    1. 改造參數(shù)
    2. 執(zhí)行this._actionSubscribers中的 before 鉤子的action監(jiān)聽函數(shù)
      • before 表示訂閱處理函數(shù)的被調(diào)用時(shí)機(jī)應(yīng)該在一個(gè) action 分發(fā)之前調(diào)用
    3. 執(zhí)行action函數(shù)
      • this._actions[type] 數(shù)組長(zhǎng)度大于1芝发,就promise.all包裝,保證result是所有promise都resolve
      • this._actions[type] 數(shù)組長(zhǎng)度小于等于1苛谷,直接調(diào)用
    4. 當(dāng)所有promise都resolve后辅鲸,即所有異步都返回結(jié)果后,執(zhí)行_actionSubscribers中 after 鉤子的action監(jiān)聽函數(shù)
      • after 表示訂閱處理函數(shù)的被調(diào)用時(shí)機(jī)應(yīng)該在一個(gè) action 分發(fā)之后調(diào)用
    5. 并將最終的結(jié)果resolve出去腹殿,返回promise對(duì)象
  • subscribeAction
    • subscribeAction(handler: Function): Function
      • store.subscribeAction((action, state) => {...})
      • 訂閱 store 的 action
      • handler 會(huì)在每個(gè) action 分發(fā)的時(shí)候調(diào)用并接收 action 描述和當(dāng)前的 store 的 state 這兩個(gè)參數(shù)
      • 注意:
        • 從 3.1.0 起独悴,subscribeAction 也可以指定訂閱處理函數(shù)的被調(diào)用時(shí)機(jī)應(yīng)該在一個(gè) action 分發(fā)之前還是之后 (默認(rèn)行為是之前)
        • 即可以指定 store.subscribeAction({before: (action, state) => {},after: (action, state) => {}})

源碼代碼

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

  • 主要作用
    • 賦值 this.root 為根模塊
    • 如果模塊中存在 modules 屬性,就給父模塊的 _children 屬性對(duì)象中添加該模塊的對(duì)應(yīng)關(guān)系
// 文件目錄 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環(huán)境锣尉,就斷言傳入的options中的各個(gè)屬性對(duì)象的每個(gè)key對(duì)于應(yīng)的value的類型刻炒,如果不是應(yīng)該有的類型,就拋錯(cuò)
    }

    // dev環(huán)境并且傳入的options符合斷言要求 或者 prod環(huán)境
    // 則進(jìn)入下面代碼

    const newModule = new Module(rawModule, runtime) // ---------------------------------------- 分析1
    // 創(chuàng)建一個(gè) module 實(shí)例
    // module
      // 實(shí)例屬性 runtime, _children, _rawModule, state
      // 原型屬性  getChild悟耘,addChild落蝙,namespaced,update暂幼,forEachGetter ...等

    if (path.length === 0) {
      this.root = newModule
      // 數(shù)組長(zhǎng)度是0筏勒,就 new Module實(shí)例賦值給 root 屬性
    } else {
      // 問(wèn)題:什么時(shí)候path.length !== 0
      // 答案:下面會(huì)判斷是否有 modules 屬性,當(dāng)存在modules屬性時(shí)旺嬉,會(huì)再次調(diào)用register管行,此時(shí)長(zhǎng)度不為0

      const parent = this.get(path.slice(0, -1)) // -------------------------------------------- 分析2
      // parent:獲取父module

      parent.addChild(path[path.length - 1], newModule) // ------------------------------------- 分析3
      // 給父module._children對(duì)象中添加key和value,即父modlue的子module
    }

    if (rawModule.modules) {
      // 如果該module中存在modules對(duì)象邪媳,就遍歷modules
      forEachValue(rawModule.modules, (rawChildModule, key) => { // --------------------------- 分析4
        this.register(path.concat(key), rawChildModule, runtime)
        // path.concat(key) => 類似 [module1],[module2]捐顷,注意concat不會(huì)改變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 對(duì)象
      // 用來(lái)存放 modules 屬性中的所有子moudle
      // key => moudle的名字
      // value => module對(duì)象荡陷,里面可能有state,mutations,actions,getters等
  
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule
    // 當(dāng)前模塊
  
    const rawState = rawModule.state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
    // state
      // 函數(shù):就調(diào)用,返回stated對(duì)象
      // 對(duì)象:直接賦值
  }
  
   addChild (key, module) {
    this._children[key] = module
  }
  
  getChild (key) {
    return this._children[key]
  }
}
  • 分析2
  get (path) {
    // 當(dāng)path是空數(shù)組時(shí)迅涮,[].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實(shí)例的_children屬性對(duì)象中添加映射
    // key => moudle名
    // value => module對(duì)象
}
  • 分析4
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
  // 遍歷obj
    // fn(value, key)
    // 將obj中的 key 作為第二個(gè)參數(shù)
    // 將obj中的 value 作為第一個(gè)參數(shù)
}

(2) Store類的constructor

Store類的構(gòu)造函數(shù) - 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存在废赞,就自動(dòng)安裝
    }

    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() 前調(diào)用
        // 是否存在promise,因?yàn)関uex依賴promise
        // Store必須通過(guò)new命令調(diào)用
    }

    const {
      plugins = [], // 解構(gòu)賦值 plugins 和 strict叮姑,并賦默認(rèn)值
      strict = false
    } = options

    // store internal state
    this._committing = false  
    // _committing
      // 標(biāo)志位唉地,默認(rèn)是false
      // 修改state都會(huì)用 _withCommit 函數(shù)包裝,在執(zhí)行修改state函數(shù)中传透,this._committing=true,修改后置為false
      // 用來(lái)判斷是否通過(guò)mutation函數(shù)修改state耘沼,只有通過(guò)mutation修改state才是合法的
      // 比如在合并state的操作中會(huì)用到
  
    this._actions = Object.create(null)
    // _actions
      // 存放所有moudle的action
      // {
      //   cart/addProductToCart: [?], f指的是 wrappedActionHandler (payload) 
      //   cart/checkout: [?],
      //   products/getAllProducts: [],
      // }
  
    this._actionSubscribers = []
    // _actionSubscribers
      // 收集action監(jiān)聽函數(shù)
      // 注意區(qū)分:
        // this._subscribers = [] mutation監(jiān)聽函數(shù)
  

    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)
      // }
      // 注意:!V煅巍H亨汀!1铡?衩亍!G T甙怼!O哿瘛!T烁摇PB亍!4荨F!X苑健Q虼瘛!E慰场3韭稹!=阶2谴贰!=酢G苊场M位巍!=榻佟;胀铩!W稀O栈妗!;赜摇B≡病!O杷浮C煅酢!
        // 1. this._wrappedGetters 在resetStoreVM(this, state)會(huì)用到 
        // 2. 注意區(qū)分 ( store.getters ) 和 ( store._wrappedGetters)

    this._modules = new ModuleCollection(options)
    // _modules
      // ModuleCollection 類主要就是收集所有的moudle
      // {
      //   root: {
      //     runtime: false,
      //     state: {}, // 注意此時(shí)還沒(méi)有合并state
      //     _children: { // 把moudules中的module放入父模塊的 _children 屬性中
      //       cart: Module,
      //       products: Module,
      //     },
      //     _rawModule: 根module
      //   }
      // }

    this._modulesNamespaceMap = Object.create(null)
    // _modulesNamespaceMap
      // namespace 和 mdule 的一一映射對(duì)象
      // {
      //   cart/: Module 
      //   products/: Module
      // }

    this._subscribers = []
    // _subscribers
      // mutation監(jiān)聽函數(shù)
      // 注意區(qū)分:
        // this._actionSubscribers = [] action監(jiān)聽函數(shù)
  
    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函數(shù)的this到store實(shí)例上
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
       // 綁定commit函數(shù)的this到store實(shí)例上
    }

    // strict mode
    this.strict = strict
    // 嚴(yán)格模式
      // 默認(rèn)是 flase
      // 嚴(yán)格模式下蹬屹,只能通過(guò)mutation修改state
      // 在生產(chǎn)環(huán)境中建議關(guān)閉侣背,因?yàn)閲?yán)格模式會(huì)深度監(jiān)測(cè)狀態(tài)樹來(lái)檢測(cè)不合規(guī)的狀態(tài)變更,有性能損耗

    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)  // --------------------------------- 下面會(huì)分析

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state) // ----------------------------------------------------------- 下面會(huì)分析

    // apply plugins
    plugins.forEach(plugin => plugin(this))
    // 循環(huán)遍歷插件慨默,傳入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
  // 當(dāng)path數(shù)組長(zhǎng)度是0贩耐,則是根module

  const namespace = store._modules.getNamespace(path)
  // 獲取 namespace => module名 + /
  //                => 或者 ''

  // register in namespace map
  if (module.namespaced) {
    // module.namespaced
      // 每個(gè)module可以有namespaced屬性,是一個(gè)布爾值
      // 表示開啟局部module命名
      // 命名空間官網(wǎng)介紹 (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('/')}`)
      // 重復(fù)了
    }
    store._modulesNamespaceMap[namespace] = module
    // _modulesNamespaceMap
      // 建立 module 和 nameSpace 的映射關(guān)系
      // key : namespace
      // vlaue: module
        // {
        //   cart/: Module 
        //   products/: Module
        // }
  }

  // set state
  if (!isRoot && !hot) {
    // 不是根模塊 并且 hot為flase厦取,才會(huì)進(jìn)入判斷
  
    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中的每一個(gè)mutations中的的mutation函數(shù)添加到 _mutations 對(duì)象上
    // _mutations 對(duì)象如下的格式
    // {
    //   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中的每一個(gè)actions中的的action函數(shù)添加到 _actions 對(duì)象上
    // _actions 對(duì)象如下的格式
    // {
    //   cart/addProductToCart: [?], f指的是 wrappedActionHandler (payload) 
    //   cart/checkout: [?]
    //   products/getAllProducts: []
    // }
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
    // 把所有modul中的每一個(gè)getter函數(shù)添加到 _wrappedGetters 對(duì)象上
    // 存放所有module中的getter
    // {
    //   cart/cartProducts: ? wrappedGetter(store)
    //   cart/cartTotalPrice: ? wrappedGetter(store)
    // }
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
    // 循環(huán)遍歷module._children對(duì)象潮太,并在每次循環(huán)中執(zhí)行 installModule 方法
  })
}

(4) resetStoreVM(this, this.state, hot)

function resetStoreVM (store, state, hot) {
  // resetStoreVM
    // 參數(shù)
      // store
      // state
      // hot

  const oldVm = store._vm
  // oldVm 緩存舊的store._vm

  // bind store public getters
  store.getters = {}
  // 在 store 實(shí)例上添加 getters 屬性對(duì)象,初始值是一個(gè)空對(duì)象
  // 注意:
    // 1. 區(qū)分 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) => {
    // 循環(huán)wrappedGetters虾攻,將 value 和 key 作為參數(shù)傳入forEachValue的第二個(gè)參數(shù)函數(shù)

    computed[key] = partial(fn, store)
    // 1. partial是這樣一個(gè)函數(shù)
      // function partial (fn, arg) {
      //   return function () {
      //     return fn(arg)
      //   }
      // }
    // 2. 即 computed[key] = () => fn(store) 
      // fn 就是具體的getter函數(shù)

    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // 可枚舉
    })
    // 1. 給 store.getters 對(duì)象添加 key 屬性
    // 2. 訪問(wèn) stroe.getters[key] = store._vm[key]
      // 即訪問(wèn) store.getter.aaaa 相當(dāng)于訪問(wèn) 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()時(shí)不會(huì)有警告

  store._vm = new Vue({
    data: {
      ?state: state // 11. 將傳入的state賦值給data中的 ?state 屬性
    },
    computed // 22. 將computed變狼賦值給Vue中的computed屬性 (computed[key] = () => fn(store))
  })
  // store._vm = new Vue(...)
    // 經(jīng)過(guò)上面 1122 使得 state和() => fn(store) 具有響應(yīng)式
  
  Vue.config.silent = silent
  // 關(guān)閉取消警告

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
    // 使能嚴(yán)格模式,保證修改store只能通過(guò)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實(shí)例
      // 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)
    // 構(gòu)造commit需要的參數(shù)類型
    // 1. store.commit('increment', 10)
    // 2. store.commit('increment', {amount: 10}) 
    // 3. store.commit({type: 'increment',amount: 10})
    // 就是將第 3 種情況構(gòu)造成第 2 種的情況

    const mutation = { type, payload }

    const entry = this._mutations[type]
    // entry 找到需要提交的mutation函數(shù)組成的數(shù)組奇钞,是一個(gè)數(shù)組,數(shù)組種就是包裝過(guò)后的mutation handle函數(shù)

    if (!entry) {
      // 沒(méi)找到該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)
        // 傳入?yún)?shù)執(zhí)行mutation handle 函數(shù)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))
      // 淺拷貝 this._subscribers 然后遍歷該數(shù)組景埃,調(diào)用里面的subscribe函數(shù) 
      // 即更改state后需要響應(yīng)視圖等

    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)
    // 構(gòu)造commit需要的參數(shù)類型
    // 1. store.dispatch('increment')
    // 2. store.dispatch('incrementAsync', {amount: 10})
    // 3. store.dispatch({type: 'incrementAsync', amount: 10})
    // 就是將第 3 種情況構(gòu)造成第 2 種的情況

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      // 沒(méi)找到該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監(jiān)聽函數(shù)
        // before 表示訂閱處理函數(shù)的被調(diào)用時(shí)機(jī)應(yīng)該在一個(gè) action 分發(fā)之前調(diào)用
    } 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)
    // 長(zhǎng)度大于1,promise.all()保證result所有resolve
    // 長(zhǎng)度小于等于1顶别,直接調(diào)用

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
          // after 表示訂閱處理函數(shù)的被調(diào)用時(shí)機(jī)應(yīng)該在一個(gè) action 分發(fā)之后調(diào)用
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
        // resolve最終結(jié)果
      }, 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如何使用
官網(wǎng)的例子

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

當(dāng)映射的計(jì)算屬性的名稱與 state 的子節(jié)點(diǎn)名稱相同時(shí)谷徙,我們也可以給 mapState 傳一個(gè)字符串?dāng)?shù)組
computed: mapState([
  // 映射 this.count 為 store.state.count
  'count'
])
  • 源碼
    • 把state構(gòu)造成computed對(duì)象返回
    • 根據(jù)namespace把 ( 局部的state和getter作為參數(shù) ) 傳給傳入的參數(shù)對(duì)象的 ( 屬性函數(shù) )
export const mapState = normalizeNamespace((namespace, states) => {
  // normalizeNamespace
    // 返回改裝參數(shù)后的,f(namespace, states)
    // 改成下面的參數(shù)形式
      // ...mapState('some/nested/module', {
      //   a: state => state.a,
      //   b: state => state.b
      // })
  const res = {}
  if (__DEV__ && !isValidMap(states)) {
    // 如果是dev環(huán)境 并且 states 不是是一個(gè)對(duì)象或者一個(gè)數(shù)組
    // 報(bào)錯(cuò)

    // 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是數(shù)組筋夏,返回一個(gè)數(shù)組蒂胞,每個(gè)成員是一個(gè)對(duì)象({ key: key, val: key })
  // 2. 如果states是對(duì)象,返回一個(gè)數(shù)組条篷,每個(gè)成員是一個(gè)對(duì)象({ 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 ) 對(duì)象中找到 ( 參數(shù)namespace ) 對(duì)應(yīng)的 ( 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是一個(gè)函數(shù)骗随,就調(diào)用函數(shù)val.call(this, state, getters)返回
      // val不是函數(shù)蛤织,就直接返回 state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
  // 最后返回res對(duì)象
  // res對(duì)象會(huì)作為組件的 computed
})

------

function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      // 如果 namespace 不是一個(gè)字符串
        // 說(shuō)明傳入只傳入了一個(gè)參數(shù)
        // 就把namespace='',把第一個(gè)不是字符串的參數(shù)賦值給第二個(gè)參數(shù)
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
      // 沒(méi)有 / 則添加
    }
    return fn(namespace, map)
    // 返回轉(zhuǎn)換參數(shù)過(guò)后的 fn
  }
}

------

function getModuleByNamespace (store, helper, namespace) {
  // 1. getModuleByNamespace(this.$store, 'mapState', namespace)

  const module = store._modulesNamespaceMap[namespace]
  // 找到namespace對(duì)應(yīng)的module
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
  // 返回module
}

------

function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
    // 不是數(shù)組或者對(duì)象鸿染,默認(rèn)返回一個(gè)數(shù)組
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
  // 1. 如果是數(shù)組指蚜,返回一個(gè)數(shù)組,每個(gè)成員是一個(gè)對(duì)象({ key: key, val: key })
  // 2. 如果是對(duì)象涨椒,返回一個(gè)數(shù)組摊鸡,每個(gè)成員是一個(gè)對(duì)象({ key:key, val: map[key] })
}

使用中的注意點(diǎn)

mapState - (帶namespace和不帶namespace)

mapGetters

mapMutations

mapActions

  • ui組件中
<template>
  <div class="vuex">
    <div>
      <div style="font-size: 30px">vuex</div>
      <div>dispatch一個(gè)action => store.dispatch({type: 'actionName', payload: ''})</div>
      <div>commit一個(gè)mutation => store.dispatch({type: 'actionName', payload: ''})</div>
     
      <div>------</div>
      <button @click="changeCount">點(diǎn)擊修改vuexModule中的count+1 </button>
      <div>{{moduleState.count}}</div>

      <div>------</div>
      <div><button @click="getName">點(diǎn)擊,發(fā)起請(qǐng)求蚕冬,獲取name數(shù)據(jù)免猾,利用Vuex actions - 不傳參數(shù)</button></div>
      <div><button @click="getName2">點(diǎn)擊,發(fā)起請(qǐng)求囤热,獲取name數(shù)據(jù)猎提,利用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", { // ---------- 第二個(gè)參數(shù)是對(duì)象,即可以修改getter的名字
      changeGetterName: "square"
    }),
    ...mapGetters("vuexModule", ['square']), // 第二個(gè)參數(shù)是數(shù)組
  },
  methods: {
    ...mapMutations('vuexModule', { 
      addCountChangeName: 'AddCount' // ------- 對(duì)象方式旁蔼,可以修改mutation的名字
    }),
    ...mapActions('vuexModule', ['getData', 'getData2']), // mapActions
    changeCount() {
      this.addCountChangeName(1) // ----------- 參數(shù)將作為mutation(state, payload)的payload
    },
    getName() {
      this.getData() // ----------------------- 不傳參給action函數(shù)锨苏,處理異步
    },
    getName2() {
      this.getData2({ // ----------------------- 傳參給action函數(shù),處理異步
        url: "/home",
        method: "get"
      })
    }
  },
  mounted() {
    console.log(
      this.rootState.vuexModule.count,
      "沒(méi)有namespace的mapState獲取的state - 訪問(wèn)coount"
    );
    console.log(
      this.moduleState.count,
      "具有namespace的mapState獲取的state - 訪問(wèn)count"
    );
    console.log(this.changeGetterName, 'mapGetters第二個(gè)參數(shù)是對(duì)象');
    console.log(this.square, 'mapGetters第二個(gè)參數(shù)是數(shù)組');
  }
};
</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函數(shù)
      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函數(shù)
      // action(context, payload)
        // 第一個(gè)參數(shù): context和store具有相同的api
        // 第二個(gè)參數(shù):payload是dispatch一個(gè)action穿過(guò)來(lái)的參數(shù)
      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官網(wǎng)文檔 https://vuex.vuejs.org/zh/

川神 較全面 https://juejin.im/post/6844904001192853511
2.3.1版本Vuex,過(guò)程詳細(xì):https://juejin.im/post/6844903495263322119
yck https://juejin.im/post/6844903676721496071

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棺聊,一起剝皮案震驚了整個(gè)濱河市伞租,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌限佩,老刑警劉巖葵诈,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祟同,居然都是意外死亡驯击,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門耐亏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沪斟,你說(shuō)我怎么就攤上這事广辰。” “怎么了主之?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵择吊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我槽奕,道長(zhǎng)几睛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任粤攒,我火速辦了婚禮所森,結(jié)果婚禮上囱持,老公的妹妹穿的比我還像新娘。我一直安慰自己焕济,他們只是感情好纷妆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晴弃,像睡著了一般掩幢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上上鞠,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天际邻,我揣著相機(jī)與錄音,去河邊找鬼芍阎。 笑死世曾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的能曾。 我是一名探鬼主播度硝,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寿冕!你這毒婦竟也來(lái)了蕊程?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤驼唱,失蹤者是張志新(化名)和其女友劉穎藻茂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玫恳,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辨赐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了京办。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掀序。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惭婿,靈堂內(nèi)的尸體忽然破棺而出不恭,到底是詐尸還是另有隱情,我是刑警寧澤财饥,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布换吧,位于F島的核電站,受9級(jí)特大地震影響钥星,放射性物質(zhì)發(fā)生泄漏沾瓦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一屯掖、第九天 我趴在偏房一處隱蔽的房頂上張望刚梭。 院中可真熱鬧,春花似錦暮顺、人聲如沸乖篷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撕蔼。三九已至豁鲤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲸沮,已是汗流浹背琳骡。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讼溺,地道東北人楣号。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像怒坯,于是被迫代替她去往敵國(guó)和親炫狱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 寫在前面 因?yàn)閷?duì)Vue.js很感興趣剔猿,而且平時(shí)工作的技術(shù)棧也是Vue.js视译,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue...
    染陌同學(xué)閱讀 1,667評(píng)論 0 12
  • vuex使用方法: 根據(jù)vuex使用方法分析: 1、vuex使用是一個(gè)插件归敬。 2酷含、需要實(shí)現(xiàn)vuex構(gòu)造函數(shù)Stor...
    仰寒天閱讀 293評(píng)論 0 1
  • 參考資料: https://vuex.vuejs.org/zh/ https://scrimba.com/g/gv...
    yywfy的昵稱閱讀 683評(píng)論 0 0
  • 狀態(tài)管理模式,依賴Promise單項(xiàng)數(shù)據(jù)流 多組件共享狀態(tài)時(shí)汪茧,單項(xiàng)數(shù)據(jù)流會(huì)被破壞 ===》抽取組件的共享狀態(tài)椅亚。以一...
    貓小柳同學(xué)閱讀 132評(píng)論 0 0
  • vue 1、 你知道vue的模板語(yǔ)法用的是哪個(gè)web模板引擎的嗎舱污?說(shuō)說(shuō)你對(duì)這模板引擎的理解 Vue使用了Musta...
    Aniugel閱讀 9,559評(píng)論 3 21