jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

我看Vuex(三) #49

Open jyzwf opened 6 years ago

jyzwf commented 6 years ago

这是对 我看Vuex(二) 里面的一些函数的介绍,感觉放在二里面,层次显得不清楚

getNamespace

// module/module-collection.js
getNamespace(path) {
    // 当根模块到前模块的路径
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      // 如果模块设置了命名空间,就取从根模块到该模块的key值作为命名空间
      // 上面 moduleC 如果设置了命名空间,那么 moduleC 的 namespace 为 “moduleA/moduleC”
      // 这里为什么不直接调用模块的 _rawModule.namespaced 呢????
      return namespace + (module.namespaced ? key + '/' : '') 
    }, '')
  }

这里注意一下,这个module 是一个模块实例,所以当他获取 namespace 属性时是调用下面的代码,还有一点就是 根模块的 namespace 是 空字符串

// module.js
 get namespaced () {  // _rawModule 保存了模块的对象描述
    return !!this._rawModule.namespaced
  }

makeLocalContext

function makeLocalContext(store, namespace, path) {
  const noNamespace = namespace === ''   // 判断是否有命名空间
    // 这里是在本地dispatch / commit
  // 如下面例子:
  /* actions: {
    actionA(){},
    actionB({
      dispatch,
      commit
    }) {
      return dispatch('actionA').then(() => {
        commit('someOtherMutation')
      })
    } */
  // 所以说,如果在组件实例中 dispatch ,就要加上模块名
  const local = {
    // 如果没有命名空间,就直接获取 store的dispatch
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      // unifyObjectStyle :获取正确的 type / payload /options
      // Actions 支持同样的载荷方式和对象方式进行分发:
      const args = unifyObjectStyle(_type, _payload, _options)
      const {
        payload,
        options
      } = args
      let {
        type
      } = args

      // options 不存在,或者其root 属性不存在
      // 因为vuex 支持下面功能:
      // 需要在全局命名空间内分发 action 或提交 mutation,
      // 将 { root: true } 作为第三参数传给 dispatch 或 commit 即可
      // 这里是不需要分发的情况
      if (!options || !options.root) {
        type = namespace + type // 获取命名空间下的 action 
        // 使用命名空间
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) { // 确保 dispatch 的 action存在
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }
      // 这里已经对type 做了命名空间的处理,
      // 如果传入了 { root: true } ,这里的 type 就是没有加上 namespace 的,
      // 如果没有传入 { root: true },这里的 type 经过上面的 if 处理,也就可以正确触发了 
      return store.dispatch(type, payload)
    },

    commit: noNamespace ? 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: {
      get: noNamespace ?
        () => store.getters : // 没有,直接获取getters 
        () => makeLocalGetters(store, namespace) // 有,获取自己本模块的 getters 
    },
    state: {
      // 获取嵌套的state
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

unifyObjectStyle

makeLocalGetters


function makeLocalGetters(store, namespace) {
  const gettersProxy = {}
  // 获取命名空间长度
  const splitPos = namespace.length
  // 循环每个 getters 
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    // 跳过没有匹配与命名空间不匹配的 getter
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    // 提出 getter 类型
    // 有 namespace 如:namespace 为 moduleA/moduleC/,type 为 moduleA/moduleC/cGetter,则 localType 为 cGetter
    // 没有 namespace , 或者说那么spacename ='' ,type 为 cGetter,则 localType 为 cGetter
    // slice 不改变原字符串
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type], // 获取原字符串所对应的 getter
      enumerable: true
    })
  })

  return gettersProxy
}

registerMutation

// 注册各个模块的 mutaations 方法到 store._mutations 中,每个type对应一个数组
function registerMutation(store, type, handler, local) {
  // 将相同的type放入到同一个数组中,这是因为在没有命名空间的情况下,各个模块会有相同的 mutation
  // 这样把这些 mutations 注册到全局,commit(type) 时候,就全部触发
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler(payload) { // 获取负载,只有它是需要用户传进来的
    handler.call(store, local.state, payload)
  })
}

registerAction

// 注册各个模块的 actions 到store._actions
function registerAction(store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  // 这里要说明下,由于 action 里是执行异步的地方,所以,如果没有命名空间的情况下
  // 多个相同 type 的action 加入到 store._actions[type],dispatch(type) 的时候,
  // 就要等这几个 action 都完成后才能 返回结果,所以这里用来 Promise 来处理,于此同时,
  // 在dispatch(type) 里面,也会使用 Promise.all(),来等待所有结果返回
  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)
    }

    // 调用开发者工具,并在错误的时候触发 vuex:error
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

registerGetter

// 注册各个模块的 getters 到store._wrappedGetters
function registerGetter(store, type, rawGetter, local) {
  // getter 不能重复,因为他是依靠 vue 的computed 属性,computed 属性不能重复,这个也就不能重复
  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
    )
  }
}

在上面和前面有不少地方调用了 _withCommit 函数,,这个函数他有啥妙用呢? 他就是防止 state 被除了 mutation 以外的函数或者直接被修改,这个函数在 store 类里面,要结合 enableStrictMode 函数来看

_withCommit(fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }

function enableStrictMode(store) {
  store._vm.$watch(function () {
    return this._data.$$state
  }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, {
    deep: true,
    sync: true
  })
}

从上面函数知道,它只是改变 this._commiting 的状态,在 enableStrictMode 里监听 state。我们知道 vuex 规定只有在 mutation 函数才能修改 state,这里当 _withCommit 执行时,会执行 传入的 fn 函数,然后 fn 修改了 state ,导致 enableStrictMode 里的 watch检测到了变化,执行后面的函数,在里面先进行判断 this._commiting 是否是 true,否则就会抛出错误,这样就做到了很好的保证

注:enableStrictMode 函数只有在 strict = true,才会执行,所以开发环境下,最好开启

总结

总的来说就是把所有的 gettermutationaction 放到 store 上,然后做相应的操作