Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

我看Vuex(三) #49

Open
jyzwf opened this issue Feb 22, 2018 · 0 comments
Open

我看Vuex(三) #49

jyzwf opened this issue Feb 22, 2018 · 0 comments

Comments

@jyzwf
Copy link
Owner

jyzwf commented Feb 22, 2018

这是对 我看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 上,然后做相应的操作

@jyzwf jyzwf changed the title 我看Vue(三) 我看Vuex(三) Feb 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant