Vuex 的構(gòu)建原理


解析源碼前,我們先來簡單的了解下Vuex喂分。
Vuex 是為 Vue 量身定做的狀態(tài)管理狀態(tài)管理模式锦庸,目的是解決多個組件共享狀態(tài)帶來的復(fù)雜數(shù)據(jù)流難以為維護(hù)的問題。簡單的說蒲祈,就是在多個組件間更工程化甘萧、簡潔明了的共享數(shù)據(jù)處理。
vuex模式圖

一梆掸、項(xiàng)目目錄

首先扬卷,讓我先瀏覽下項(xiàng)目目錄。如下:

   |— src  源代碼
       |— module  模塊相關(guān)
            |—module-collection.js
            |—module.js
        |— plugins
            |— devtool.js
            |— logger.js
        |— helpers.js  輔助函數(shù)
        |— index.esm.js  入口文件酸钦,使用ES Module
        |— index.js  入口文件
        |— mixin.js  混淆
        |— store.js  核心模塊
        |— util.js  工具函數(shù)

二怪得、入口

接著,我們從入口文件 index.js 開始卑硫,逐級的去了解 Vuex 的構(gòu)建徒恋。

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

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

在入口文件中,我們看到 vuex 這里導(dǎo)出了一個 object欢伏,之中包含了核心模塊Store入挣,裝載模塊方法install,版本version硝拧,輔助函數(shù)mapState径筏、mapMutations、mapGetters障陶、mapActions滋恬,命名空間輔助函數(shù)createNamespacedHelpers。

三抱究、注入安裝

我們的都知道恢氯,Vue.js提供了Vue.use方法用來給Vue.js安裝插件,通過調(diào)用插件內(nèi)部的 install 方法來安裝的(如果插件是對象)。
我們來看這里 install 的實(shí)現(xiàn)勋拟。

 function install (_Vue) {
  if (Vue && _Vue === Vue) {  // 檢測 Vue 是否存在遏暴,且和已安裝的是否相同,防止重復(fù)安裝
    if (process.env.NODE_ENV !== 'production') {  // 判斷是開發(fā)環(huán)境指黎,輸出日志到控制臺
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue  // 保存當(dāng)前 Vue
  applyMixin(Vue)  // 調(diào)用 applyMixin 將 Vuex 混入 Vue
}

讓我們再來看下 applyMixin朋凉,其實(shí)在mixin.js 中只實(shí)現(xiàn)了一個混淆,就是將 vuex 混淆注入 vue醋安。

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

  if (version >= 2) {
    // 如果 vue 版本2.0以上杂彭,調(diào)用vue.mixin,在 beforeCreate 的時(shí)候執(zhí)行 vuexInit 注入 vuex
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // 如果 vue 1.x 的版本吓揪,將 vuexInit 放入 vue 的 _init 方法中
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit () {
    const options = this.$options
    // 獲取實(shí)例上的 store
    if (options.store) {
      // 如果 store 是個函數(shù)亲怠,執(zhí)行它獲得 store
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      // 當(dāng)前組件上沒有 store,就從父組件上獲取 store
      this.$store = options.parent.$store
    }
  }
}

這里對于 vue 做了區(qū)分柠辞,在 vue 2 以上的版本調(diào)用 Vue.mixin 直接混入到 beforeCreate 鉤子中团秽,1.0 放入 _init 方法中。
在 vuexInit 方法中叭首,獲取當(dāng)前組件 store习勤,如果不存在再從父組件獲取,保證了 store 的一致性焙格,同時(shí)把 _this.$store 指向跟 store 實(shí)例图毕。

四、Store

(1)構(gòu)造函數(shù)

constructor (options = {}) {
    // 判斷注入vuex
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    
    // 非生產(chǎn)環(huán)境輸出日志
    if (process.env.NODE_ENV !== 'production') {
      // 判斷 Vuex 是否已混入 Vue
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      // 判斷是否支持 Promise
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      // 判斷是否需要使用 new 關(guān)鍵字來創(chuàng)建 Store 實(shí)例
      assert(this instanceof Store, `store must be called with the new operator.`)
    }
    
    const {
      plugins = [],  // Vuex 的插件方法眷唉,接收 Store 作為唯一參數(shù)
      strict = false  // 嚴(yán)格模式予颤,在嚴(yán)格模式下,通過提交 mutation 之外的方法改變 state 都會報(bào)錯
    } = options

    this._committing = false  // 是否正在commit
    this._actions = Object.create(null)  // actions
    this._actionSubscribers = []  // actions 訂閱者
    this._mutations = Object.create(null) // mutations
    this._wrappedGetters = Object.create(null) // getters
    this._modules = new ModuleCollection(options) // modules收集器
    this._modulesNamespaceMap = Object.create(null) // 根據(jù)命名空間收集modules
    this._subscribers = []  // 訂閱者
    this._watcherVM = new Vue()  // 觀察者 Vue 實(shí)例

    // 把 commit 和 dispatch 綁定到 store
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // 嚴(yán)格模式(嚴(yán)格模式下冬阳,state 只能通過提交 mutations 來更改蛤虐,否則會拋出錯誤)
    this.strict = strict

    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化根模塊,遞歸注冊依賴的模塊肝陪,收集所有模塊getters放進(jìn)_wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 初始化store vm驳庭,注冊 getters 和計(jì)算屬性。
    resetStoreVM(this, state)

    // 調(diào)用插件
    plugins.forEach(plugin => plugin(this))

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

可以看出來见坑,構(gòu)造器函數(shù)主要做了這幾件事情:
1嚷掠、聲明基礎(chǔ)變量
2捏检、創(chuàng)建 modules 收集器 荞驴、調(diào)用 installModule 初始化并且遞歸注冊子模塊
3、調(diào)用 resetStoreVM 來使 Store 具有“響應(yīng)式”

(2)模塊的加工

首先贯城,我們先來看看 ModuleCollection 是怎樣收集保存為 _modules 的熊楼。

export default class ModuleCollection {
  constructor (rawRootModule) {
    // 注冊模塊
    this.register([], rawRootModule, false)
  }
   
  // 根據(jù) path 來獲取模塊
  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

  // 根據(jù) path 來獲取命名空間
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }

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

  // 注冊方法
  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      // 判斷模塊是否符合規(guī)范
      assertRawModule(path, rawModule)
    }

    // 實(shí)例化
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      // 創(chuàng)建根模塊
      this.root = newModule
    } else {
      // 獲取父模塊
      const parent = this.get(path.slice(0, -1))
      // 把子模塊掛載到父模塊上
      parent.addChild(path[path.length - 1], newModule)
    }

    // 遞歸的注冊關(guān)聯(lián)模塊
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
  
  // 注銷子模塊
  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return

    parent.removeChild(key)
  }
}

可以看到,ModuleCollection 主要是將傳入的 options 加工為可用的 _modules 模塊,提供了一些方法鲫骗。
主要做了以下四件事:
1犬耻、判斷模塊是否符合規(guī)范
2、封裝模塊执泰,并遞歸的封裝子模塊掛載到父模塊上枕磁,把所有模塊種成一顆_modules樹
3、提供一些模塊可用方法

接著术吝,我們再看看一個 module 是怎樣實(shí)例化的吧计济。

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

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

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

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

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

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

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

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

主要做了以下幾件事:
1排苍、定義 _rawModule 沦寂、state 等基礎(chǔ)屬性
2、定義 namespaced 屬性來獲取模塊的 namespaced淘衙,判斷是否有命名空間
3传藏、提供模塊的一些基礎(chǔ)方法,添加彤守、刪除毯侦、獲取和更新
4、提供遍歷的工具方法

(3)模塊的注冊

平時(shí)我們使用 mutations具垫,actions 的時(shí)候叫惊,只需向 commit/dispatch 傳入一個字符串和載荷,就可以更改 state做修。
那么霍狰,vuex 是如何做到的呢。這就輪到 installModule 出馬了饰及。

// installModule 是一個私有方法蔗坯,并不在原型上。
function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length  // 判斷根模塊燎含,path 不存在即根模塊
  const namespace = store._modules.getNamespace(path)  // 獲取模塊的命名空間

  if (module.namespaced) {
    // 定義模塊的命名空間映射Map
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    // 將子模塊的 state 掛載到父模塊上
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  // 獲取執(zhí)行上下文
  const local = module.context = makeLocalContext(store, namespace, path)

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

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

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

installModule的意義是初始化根模塊然后遞歸的初始化所有模塊屏箍,并且收集模塊樹的所有g(shù)etters绘梦、actions、mutations赴魁、以及state卸奉。
主要做了這些事:
1、根據(jù)命名空間收集 getters颖御、actions榄棵、mutations
2、獲取執(zhí)行上下文來注冊 getters、actions疹鳄、mutations(下面詳說)
3拧略、遞歸的注冊子模塊的 getters、actions瘪弓、mutations

(4)dispatch 和 commit 的實(shí)現(xiàn)

我們都知道垫蛆,dispatch 和 commit 有兩種提交風(fēng)格:

// 以 commit 為例
// 標(biāo)準(zhǔn)風(fēng)格
this.$store.commit('myMutation',{name : 'Shein'});
// 對象風(fēng)格
this.$store.commit({type: 'myMutation',name : 'Shein'});

所以第一步要統(tǒng)一我們的參數(shù)

function unifyObjectStyle (type, payload, options) {
  //  判斷我們傳入的第一個參數(shù)是否為對象,
  if (isObject(type) && type.type) {
    options = payload  // options 設(shè)為第二個參數(shù)
    payload = type  // 載荷設(shè)置為第一個參數(shù)
    type = type.type  // mutation 為 type 值
  }

  if (process.env.NODE_ENV !== 'production') {
    // mutation 方法名不為 string 時(shí)腺怯,拋出錯誤
    assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

現(xiàn)在月褥,我們來看看 commit 函數(shù)

  commit (_type, _payload, _options) {
    // 校正參數(shù)并定義
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    
    const mutation = { type, payload }
    const entry = this._mutations[type]  // 獲取 mutation 入口
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      // 遍歷入口,執(zhí)行 mutation
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))  // 訂閱者

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

commit 首先矯正了參數(shù)瓢喉,然后再去通過 this._mutations[type] 獲取命名空間中所有的 mutations 進(jìn)行遍歷宁赤,執(zhí)行 handle。
那么 dispatch 呢栓票?

dispatch (_type, _payload) {
    // 同樣是先校正參數(shù)
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

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

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

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

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

與 commit 大同小異,不同的是走贪,在多個同名 actions 存在時(shí)候佛猛,內(nèi)部是調(diào)用 Promise.all 并發(fā)執(zhí)行的。
看完了 commit 和 dispatch 是怎么觸發(fā) mutation 和 action 之后坠狡,不禁要問继找,mutation 和 action 是如何執(zhí)行的,內(nèi)部又是如何調(diào)用的呢逃沿?
以 mutation 為例婴渡,再讓我們回頭看看 mutations 是如何注冊的。

module.forEachMutation((mutation, key) => {
   // 遍歷并注冊模塊內(nèi)部的 mutaions
  const namespacedType = namespace + key
  registerMutation(store, namespacedType, mutation, local)
})

我們把 registerMutation 方法抓出來看一下

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

首先通過 type凯亮,獲取到了所有同名方法的入口(沒有的話會新建一個入口)边臼,再將執(zhí)行方法 handler push 到入口中,這里使用了 call 方法假消,把 this 指向到 store. mutation 接收兩個參數(shù)柠并,分別是 state 和 payload。
我們再來看下 action 的注冊富拗。

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)  // 非 promise 轉(zhuǎn)為 promise
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

和 commit 不同臼予,action 的第一個參數(shù)會傳入 context ,所以在 registerAction 中會通過 local 把dispatch 等方法傳進(jìn)來啃沪。其次粘拾,如果 actions 不是 Promise,會強(qiáng)制轉(zhuǎn)為 promise谅阿,是為了多個同名 action 并發(fā)的時(shí)候半哟,使用 Promise.all() 來處理。
那么問題來了签餐,local 有什么用寓涨?是怎么定義的呢?
把視線拉回 local 定義的地方氯檐。

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

  const local = {
    // 不啟用命名空間戒良,走向 store 的dispatch,啟用命名空間冠摄,走向模塊內(nèi)部 dispatch糯崎。
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      
      // 判斷 options.root 不存在,為分發(fā)的 action 加上命名空間
      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

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

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

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

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    // 定義 getters河泳,存在命名空間則通過 makeLocalGetters 代理到模塊內(nèi)部的 getters
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      // 獲取到模塊內(nèi)部 state
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

local 里會判斷是否有命名空間沃呢,有的話,返回模塊內(nèi)部的 dispatch拆挥、commit 和 getters薄霜,state 固定返回模塊的 state。
我們再來看看 makeLocalGetters 怎么獲取局部 getters纸兔。

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

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

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

創(chuàng)建局部的getters就是一個代理的過程惰瓜,在使用模塊內(nèi)使用(沒有加上命名空間的)getters的名字,會被代理到汉矿,store實(shí)例上那個真正的(全名的)getters崎坊。
最后,來看一下 getNestedState

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

比較簡單了洲拇,通過 path 獲取當(dāng)前模塊的 state奈揍。
local 到此已經(jīng)定義完畢,那么它是用來做什么的呢赋续。

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

在注冊 mutation 的時(shí)候打月,我們傳入的是模塊內(nèi)部的state。
2.注冊 actions

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

action 中傳入的 context蚕捉,都會被代理到模塊本身的 action 上奏篙。

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

在 getters 中,我們可以任意的組裝數(shù)據(jù)迫淹,既可以獲取局部的 state 秘通、getters ,也可以獲取全局的敛熬。
到此肺稀,模塊相關(guān)的已經(jīng)告一段落。接下來我們來看下 Vuex 怎么依賴Vue核心實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”应民。

(5)響應(yīng)式更新數(shù)據(jù) resetStoreVM

function resetStoreVM (store, state, hot) {
  // 保存舊實(shí)例
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure enviroment.
    // 遍歷 wrappedGetters 话原,為每個 getters 設(shè)置 get夕吻,映射到 store.vm[key]
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  /* Vue.config.silent暫時(shí)設(shè)置為true的目的是在new一個Vue實(shí)例的過程中不會報(bào)出一切警告 */
  const silent = Vue.config.silent
  Vue.config.silent = true
  /*  這里new了一個Vue對象,運(yùn)用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊state以及computed*/
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  /* 使能嚴(yán)格模式繁仁,保證修改store只能通過mutation */
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    /* 解除舊vm的state的引用涉馅,以及銷毀舊的Vue對象 */
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

resetStoreVM首先會遍歷wrappedGetters,使用Object.defineProperty方法為每一個getter綁定上get方法黄虱,這樣我們就可以在組件里訪問this.$store.getter.test就等同于訪問store._vm.test稚矿。

之后Vuex采用了new一個Vue對象來實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”,運(yùn)用Vue.js內(nèi)部提供的數(shù)據(jù)雙向綁定功能來實(shí)現(xiàn)store的數(shù)據(jù)與視圖的同步更新捻浦。

這兩步執(zhí)行完以后晤揣,我們就可以通過this.$store.getter.test訪問vm中的test屬性了。

五朱灿、總結(jié)

Vuex 主要原理構(gòu)建的解析就到此結(jié)束啦昧识。
總的來說,Vuex 的代碼不是很多盗扒。但麻雀雖小滞诺,五臟俱全,里面的設(shè)計(jì)模式還是十分優(yōu)秀环疼,值得我們學(xué)習(xí)的习霹。我總結(jié)幾點(diǎn),在項(xiàng)目中可能會遇到的情況吧(其實(shí)文檔里大都有寫- -):
1炫隶、模塊內(nèi)提交 mutation 和 action淋叶,需要更改root狀態(tài),或者提交root的commit伪阶,傳入options: {root: true}煞檩。
2、沒有聲明嚴(yán)格模式的情況下栅贴,state 可以被直接修改的斟湃,需要注意??,聲明了嚴(yán)格模式后檐薯,任何非提交 mutation 的對 state 都會拋出錯誤凝赛,這能保證所有的狀態(tài)變更都能被調(diào)試工具跟蹤到。
3坛缕、分發(fā) action 依然會存在“競態(tài)”的問題墓猎,需要注意業(yè)務(wù)邏輯的先后,之所以要分發(fā) action 赚楚,是為了每個 mutation 提交后會立即得到一個新的狀態(tài)毙沾。
4、如果不設(shè)置namespaced: true宠页,不同模塊相同的 mutation 可以同時(shí)觸發(fā)左胞,相同模塊同名 mutation 后面的會覆蓋前面的寇仓。actions 同理。

還有烤宙,最好不要用 360 查殺 Vuex 的項(xiàng)目遍烦。會報(bào)毒!C爬谩H橛洹兄淫!



ENDING....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屯远,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捕虽,更是在濱河造成了極大的恐慌慨丐,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泄私,死亡現(xiàn)場離奇詭異房揭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)晌端,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門捅暴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咧纠,你說我怎么就攤上這事蓬痒。” “怎么了漆羔?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵梧奢,是天一觀的道長。 經(jīng)常有香客問我演痒,道長亲轨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任鸟顺,我火速辦了婚禮惦蚊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讯嫂。我一直安慰自己养筒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著潦闲,像睡著了一般泡挺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巫湘,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天装悲,我揣著相機(jī)與錄音,去河邊找鬼尚氛。 笑死诀诊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阅嘶。 我是一名探鬼主播属瓣,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讯柔!你這毒婦竟也來了抡蛙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤魂迄,失蹤者是張志新(化名)和其女友劉穎粗截,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捣炬,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熊昌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了湿酸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婿屹。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖推溃,靈堂內(nèi)的尸體忽然破棺而出昂利,到底是詐尸還是另有隱情,我是刑警寧澤美莫,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布页眯,位于F島的核電站,受9級特大地震影響厢呵,放射性物質(zhì)發(fā)生泄漏窝撵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一襟铭、第九天 我趴在偏房一處隱蔽的房頂上張望碌奉。 院中可真熱鬧,春花似錦寒砖、人聲如沸赐劣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魁兼。三九已至,卻和暖如春漠嵌,著一層夾襖步出監(jiān)牢的瞬間咐汞,已是汗流浹背盖呼。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留化撕,地道東北人几晤。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像植阴,于是被迫代替她去往敵國和親蟹瘾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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

  • 寫在前面 因?yàn)閷ue.js很感興趣掠手,而且平時(shí)工作的技術(shù)棧也是Vue.js憾朴,這幾個月花了些時(shí)間研究學(xué)習(xí)了一下Vue...
    染陌同學(xué)閱讀 1,671評論 0 12
  • 上一章總結(jié)了 Vuex 的框架原理,這一章我們將從 Vuex 的入口文件開始惨撇,分步驟閱讀和解析源碼伊脓。由于 Vuex...
    你的肖同學(xué)閱讀 1,788評論 3 16
  • 目錄 - 1.什么是vuex? - 2.為什么要使用Vuex? - 3.vuex的核心概念府寒?||如何在組件中去使用...
    我跟你蔣閱讀 4,111評論 4 51
  • Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式魁衙。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)...
    白水螺絲閱讀 4,668評論 7 61
  • 每天干活都很累株搔,但每天晚上忙完的時(shí)候都想堅(jiān)持著寫一下自己的愛好剖淀。再苦再累,想成為什么樣的人都是靠自己想怎么樣做纤房。人...
    珊琦閱讀 162評論 0 2