title: Vuex入门
toc: true
date: 2018-09-23 22:30:52
categories:html
tags:vue
vuex文档学习笔记。vuex
比较经常使用的两种:shell
从https://unpkg.com/vuex下载后利用script标签在vue后引入:npm
<script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script>
或api
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>
在项目目录下运行:数组
npm install vuex --save
在模块化的打包系统中利用这种方法时,必须显式地利用Vue.use()
来安装Vuex(而script标签引入后是自动安装的):promise
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
功能:把组件的共享状态抽取出来,用一个全局单例模式管理。缓存
核心:store(仓库),它包含了应用中的大部分state(状态,驱动应用的数据源)。bash
这种全局单例模式管理和单纯的全局变量的区别:
一个栗子:
// 若是在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } }) // 触发状态变动 store.commit('increment') console.log(store.state.count) // -> 1
再次强调,使用提交 mutation ,而不是直接改变 store.state.count
,
是由于咱们想要更明确地追踪到状态的变化。
固然,使用 Vuex 并不意味着须要将全部的状态放入 Vuex。
虽然将全部的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。
若是有些状态严格属于单个组件,最好仍是做为组件的局部状态。
你应该根据你的应用开发须要进行权衡和肯定。
每一个应用只包含一个 store 实例,它包含了全部须要vuex管理的状态。
从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
// 建立一个 Counter 组件 const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }
但这种模式致使组件依赖全局状态单例。
store
选项为了解决上述模式致使的组件依赖全局状态单例的问题,
咱们能够经过在根实例中注册 store
选项——
这样 store 实例会注入到根组件下的全部子组件中,
且子组件能经过 this.$store
访问到:(需调用 Vue.use(Vuex)
)
// 根组件 const app = new Vue({ el: '#app', // 把 store 对象提供给 “store” 选项,这能够把 store 的实例注入全部的子组件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
// 子组件 const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { // 经过 this.$store 访问store return this.$store.state.count } } }
mapState
辅助函数当一个组件须要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。
为了解决这个问题,咱们可使用 mapState
辅助函数帮助咱们生成计算属性,让你少按几回键:
// 在单独构建的版本中辅助函数为 Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭头函数可以使代码更简练 // 将 `this.count` 映射为 `this.$store.state.count count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', // 为了可以使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } }) }
当映射的计算属性的名称与 state 的子节点名称相同时,咱们也能够给 mapState
传一个字符串数组。
computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])
ES6引入的新语法,由名字就能够看出来这个操做符的含义:把对象展开,
来个栗子更容易理解:
var a = {'a':1, 'b':2, 'c':3}; {...a,'d':4} // {a: 1, b: 2, c: 3, d: 4} var b = [1,2,3]; [...b,4] // [1, 2, 3, 4]
有了这个操做符,咱们就能够把mapState
函数和局部计算属性混合使用了:
computed: { // 局部计算属性 localComputed () { /* ... */ }, // 使用对象展开运算符将此对象展开混入到外部对象中 ...mapState({ // ... }) }
Vuex 容许咱们在 store 中定义“getter”(能够认为是 store 的计算属性)。
就像计算属性同样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。
Getter 会暴露为 store.getters
对象,你能够以属性的形式访问这些值;getter 在经过属性访问时是做为 Vue 的响应式系统的一部分缓存其中的。
Getter 接受 state 做为其第一个参数:
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } }) store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也能够接受其余 getter 做为第二个参数:
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } } store.getters.doneTodosCount // -> 1
你也能够经过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时很是有用。
getters: { // ... // getter 在经过方法访问时,每次都会去进行调用,而不会缓存结果。 getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } } store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
咱们能够很容易地在任何组件中使用getter:
computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }
咱们也可使用mapGetters
辅助函数将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount', // ... ]) } }
更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。
Vuex 中的 mutation 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { // 这里的事件类型为 'increment' increment (state) { // 变动状态 state.count++ } } })
但咱们不能直接调用一个 mutation 回调函数,就像前面说的,咱们只能提交 mutation。
就像是事件注册:“当触发一个类型为 increment
的 mutation 时,调用此函数。”
要唤醒一个 mutation handler,你须要以相应的 type 调用 store.commit 方法(即提交mutation):
store.commit('increment')
咱们还能够向 store.commit
传入额外的参数,即 mutation 的 载荷(payload):
// ... mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10)
在大多数状况下,载荷应该是一个对象,这样能够包含多个字段,而且记录的 mutation 会更易读:
// ... mutations: { increment (state, payload) { state.count += payload.amount } } // ... store.commit('increment', { amount: 10 })
咱们可使用对象风格的提交方式,将一个直接包含 type
属性的对象做为载荷传给 mutations :
store.commit({ type: 'increment', amount: 10 })
而且handler 无需改变:
mutations: { increment (state, payload) { state.count += payload.amount } }
由于 Vuex 的 store 中的状态是响应式的,那么当咱们使用Mutation变动状态时,监视状态的 Vue 组件也会自动更新。
所以使用 Vuex 中的 mutation 也须要像使用Vue 同样遵照一些注意事项:
提早在 store 中初始化好全部所需属性。
当须要在对象上添加新属性时,你应该
使用 Vue.set(obj, 'newProp', 123)
, 或者
以新对象替换老对象。例如利用ES6的对象展开运算符:
state.obj = { ...state.obj, newProp: 123 }
mutation 必须是同步函数。为何?请参考下面的例子:
mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
假设咱们正在 debug 一个 app 而且观察 devtool 中的 mutation 日志:
每一条 mutation 被记录,devtools 都须要捕捉到前一状态和后一状态的快照。
然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:
当 mutation 触发的时候,回调函数尚未被调用,devtools 不知道何时回调函数实际上被调用——
实质上任何在回调函数中进行的状态的改变都是不可追踪的。
咱们能够在组件中使用 this.$store.commit('xxx')
提交 mutation,
或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用(须要在根节点注入 store
):
import { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([ 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')` // `mapMutations` 也支持载荷: 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` }) } }
使用常量替代 mutation 事件类型在各类 Flux 实现中是很常见的模式。这样可使 linter 之类的工具发挥做用,同时把这些常量放在单独的文件中可让你的代码合做者对整个 app 包含的 mutation 一目了然:
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 咱们可使用 ES2015 风格的计算属性命名功能来使用一个常量做为函数名 [SOME_MUTATION] (state) { // mutate state } } })
用不用常量取决于实际状况——在须要多人协做的大型项目中,这会颇有帮助。你果真若是不想用,也彻底能够不用。
Action 相似于 mutation,不一样在于:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { // 变动状态 state.count++ } }, actions: { // 接受一个与 store 实例具备相同方法和属性的 context 对象 increment (context) { // 提交mutation context.commit('increment') } } })
实践中,咱们可使用 ES2015 的 参数解构 来简化代码(特别是咱们须要调用 commit
不少次的时候):
actions: { increment ({ commit }) { commit('increment') } }
由于action是提交mutation而不是直接变动状态,所以咱们就能够在action内部执行异步操做了:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Action 经过 store.dispatch
方法触发:
store.dispatch('increment')
Actions 支持Mutation一样的载荷方式和对象方式进行分发:
// 以载荷形式分发 store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })
也能够在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(须要先在根节点注入 store
):
import { mapActions } from 'vuex' export default { // ... 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 一般是异步的,那么如何知道 action 何时结束呢?更重要的是,咱们如何才能组合多个 action,以处理更加复杂的异步流程?
store.dispatch
能够处理被触发的 action 的处理函数返回的 Promise,
而后返回这个 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,咱们还能够以下组合 action:
// 假设 gotData() 和 gotOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
一个
store.dispatch
在不一样模块中能够触发多个 action 函数。在这种状况下,只有当全部触发函数完成后,返回的 Promise 才会执行。
因为使用单一状态树,应用的全部状态会集中到一个比较大的对象。当应用变得很是复杂时,store 对象就有可能变得至关臃肿。
为了解决以上问题,Vuex 容许咱们将 store 分割成模块(module)。每一个模块拥有本身的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行一样方式的分割:
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 的状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
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 } } }
Vuex 并不限制咱们的代码结构。可是,它规定了一些须要遵照的规则:
若是 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,咱们会但愿把 Vuex 相关代码分割到模块中。下面是项目结构示例:
├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── components │ ├── App.vue │ └── ... └── store ├── index.js # 咱们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块
有时候看不进去文档,一边总结一边看就能看进去了 :)