VueX全攻略(二)|青训营笔记

94 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

VueX

感谢官方文档,本文示例以及部分章节为原文内容。

Actions

Actions 类似于Redux 中的 Thunk ,它类似于mutation,不同之处在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

一个简单的示例:

const store new Vuex.Store({
 state: {
   count0
},
 mutations: {
   increment (state) {
     state.count++
  }
},
 actions: {
   increment (context) {
     context.commit('increment')
  }
   
   //用参数解构的写法
   increment ({ commit }) {
   commit('increment')
}
}
})
​
//调用
store.dispatch('increment')
参数

Action 接受一个与 store 实例具有相同方法、属性的 context对象,因此可以直接类似这样访问context.state 。 但是它并非store 本身!

特性

Action 与mutation最大的不同是支持异步操作

actions: {
 incrementAsync ({ commit }) {
   setTimeout(() => {
     commit('increment')
  }, 1000)
}
}
​
// 以载荷形式分发
store.dispatch('incrementAsync', {
 amount: 10
})
​
// 以对象形式分发
store.dispatch({
 type: 'incrementAsync',
 amount: 10
})
mapActions

类似于之前的map... 函数

methods: {
   ...mapActions([     'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`​     // `mapActions` 也支持载荷:     'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`  ]),
   ...mapActions({
     add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
  })
组合Action

请理解,store.dispatch 可以处理一个被触发的action的函数返回的promise ,并且,store.dispatch 仍然返回Promise

actions: {
 actionA ({ commit }) {
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       commit('someMutation')
       resolve()
    }, 1000)
  })
}
}
​
//调用
store.dispatch('actionA').then(() => {
 // ...
})
​
​
//另一个action可以写:
actions: {
 // ...
 actionB ({ dispatch, commit }) {
   return dispatch('actionA').then(() => {
     commit('someOtherMutation')
  })
}
}

如果利用async/await:

// 假设 getData() 和 getOtherData() 返回的是 Promise
​
actions: {
 async actionA ({ commit }) {
   commit('gotData'await getData())
},
 async actionB ({ dispatch, commit }) {
   await dispatch('actionA'// 等待 actionA 完成
   commit('gotOtherData'await getOtherData())
}
}

Module

如果应用很大, 可以分割成多个模块,避免全部写在一个文件内,这样会很臃肿,而且不易维护。

例如这样

const moduleA = {
 state: () => ({ ... }),
 mutations: { ... },
 actions: { ... },
 getters: { ... }
}
​
const moduleB = {
 state: () => ({ ... }),
 mutations: { ... },
 actions: { ... }
}
​
const store = new Vuex.Store({
 modules: {
   a: moduleA,
   b: moduleB
}
})
​
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
局部状态

模块内部的mutationgetter 接受 的第一个参数是模块的局部状态对象(state)

const moduleA = {
 state: () => ({
   count: 0
}),
 mutations: {
   increment (state) {
     // 这里的 `state` 对象是模块的局部状态
     state.count++
  }
},
​
 getters: {
   doubleCount (state) {
     return state.count * 2
  }
}
}

同样,对于模块内部的action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
 // ...
 actions: {
   incrementIfOddOnRootSum ({ state, commit, rootState }) {
     if ((state.count + rootState.count) % 2 === 1) {
       commit('increment')
    }
  }
}
}

对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {
 // ...
 getters: {
   sumWithRootCount (state, getters, rootState) {
     return state.count + rootState.count
  }
}
}
命名空间

可以通过添加 namespaced:true 使这个模块成为带命名空间的模块,当模块被注册后,它的所有getter,actionmutation都会根据模块的注册路径自动调整命名,这样子在嵌套的模块内部调用就只会调用模块内部的getter等,而不会注册在全局中。

const store = new Vuex.Store({
 modules: {
   account: {
     namespaced: true,
​
     // 模块内容(module assets)
     state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
     getters: {
       isAdmin () { ... } // -> getters['account/isAdmin']
    },
     actions: {
       login () { ... } // -> dispatch('account/login')
    },
     mutations: {
       login () { ... } // -> commit('account/login')
    },
​
     // 嵌套模块
     modules: {
       // 继承父模块的命名空间
       myPage: {
         state: () => ({ ... }),
         getters: {
           profile () { ... } // -> getters['account/profile']
        }
      },
​
       // 进一步嵌套命名空间
       posts: {
         namespaced: true,
​
         state: () => ({ ... }),
         getters: {
           popular () { ... } // -> getters['account/posts/popular']
        }
      }
    }
  }
}
})
命名空间内访问全局内容

如果开启了命名空间,还想访问全局内容,那么可以使用rootStaterootGetters 作为第三和第四参数传入getter,也会通过cnotext 对象的属性传入action.

若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatchcommit 即可。

modules: {
 foo: {
   namespaced: true,
​
   getters: {
     // 在这个模块的 getter 中,`getters` 被局部化了
     // 你可以使用 getter 的第四个参数来调用 `rootGetters`
     someGetter (state, getters, rootState, rootGetters) {
       getters.someOtherGetter // -> 'foo/someOtherGetter'
       rootGetters.someOtherGetter // -> 'someOtherGetter'
    },
     someOtherGetter: state => { ... }
  },
​
   actions: {
     // 在这个模块中, dispatch 和 commit 也被局部化了
     // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
     someAction ({ dispatch, commit, getters, rootGetters }) {
       getters.someGetter // -> 'foo/someGetter'
       rootGetters.someGetter // -> 'someGetter'
​
       dispatch('someOtherAction'// -> 'foo/someOtherAction'
       dispatch('someOtherAction'null, { roottrue }) // -> 'someOtherAction'
​
       commit('someMutation'// -> 'foo/someMutation'
       commit('someMutation'null, { roottrue }) // -> 'someMutation'
    },
     someOtherAction (ctx, payload) { ... }
  }
}
}

在带命名空间的模块注册全局 action

若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
 actions: {
   someOtherAction ({dispatch}) {
     dispatch('someAction')
  }
},
 modules: {
   foo: {
     namespaced: true,

     actions: {
       someAction: {
         root: true,
         handler (namespacedContextpayload)... } // -> 'someAction'
      }
    }
  }
}
}

#带命名空间的绑定函数

当使用 mapState, mapGetters, mapActionsmapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {
 ...mapState({
   a: state => state.some.nested.module.a,
   b: state => state.some.nested.module.b
})
},
methods: {
 ...mapActions([   'some/nested/module/foo', // -> this['some/nested/module/foo']()
   'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}

对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:

computed: {
 ...mapState('some/nested/module', {
   astate => state.a,
   bstate => state.b
})
},
methods: {
 ...mapActions('some/nested/module', [
   'foo'// -> this.foo()
   'bar' // -> this.bar()
])
}

而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
​
export default {
 computed: {
   // 在 `some/nested/module` 中查找
   ...mapState({
     astate => state.a,
     bstate => state.b
  })
},
 methods: {
   // 在 `some/nested/module` 中查找
   ...mapActions([
     'foo',
     'bar'
  ])
}
}