Vuex快速学习笔记。
如何引入Vuex
首先安装Vuex:
然后在src
中新建一个文件夹store
,在里面新建一个JS文件index.js
,这就是我们的Vuex的主文件(如果项目木块比较多,可以在store
中再建立moudles
文件夹,利用Vuex提供的模块拆分功能,拆分模块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── components │ ├── App.vue │ └── ... └── store ├── index.js # 我们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块
|
然后在index.js
中,引入Vuex(Vue.use(Vuex)
),创建store
和对应的state
、getter
、mutation
、action
和module
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Vue from 'vue'; import Vuex from 'vuex'; import cart from './modules/cart.js' import products from './modules/products.js'
Vue.use(Vuex);
export default new Vuex.Store({ state: { count: 0, } modules: { cart, products, } })
|
最后将创建好的store
导入到Vue实例中,在main.js
中:
1 2 3 4 5 6 7 8 9 10
| import Vue from 'vue'; import store from './store';
new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' });
|
使用Vuex的准则:
(1)应用层级的状态应该集中到单个store对象中。
(2)提交mutation
是更改状态的唯一方法,并且这个过程是同步的。
(3)异步逻辑应该封装到action
里面
(4)组件仍然可以保有局部状态,如果某些状态严格属于单个组件,最好还是作为组件的局部状态。
Store
Vuex的核心就是store
, 包含了应用中大部分的状态(state
)
一个简单的store:
1 2 3 4 5 6 7 8 9 10 11 12
|
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } })
|
muaction
就是定义在store
中,用来改变store
中state
的方法
通过提交mutation
的方式,而非直接改变store.state.count
,可以更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <body> <div id="app"> <h3>{{count}}</h3> <button @click="add">+</button> <button @click="reduce">-</button> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { add: state => state.count++, reduce: state => state.count-- } }); const vm = new Vue({ el: '#app', computed: { count: store.state.count }, methods: { add(){ store.commit('add') }, reduce(){ store.commit('reduce') } } }); </script>
|
State
Vuex是单一状态树,用一个对象就包含了全部的应用层级状态,是整个应用的唯一数据源。
首先可以通过根组件使用store
选项,将状态库从根组件注册到每一个组件中(需要提前调用Vue.use(Vuex)
):
1 2 3 4 5 6 7
| new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' });
|
在Vue组件中,获取状态的方法是在组件的计算属性中返回某个状态。有两种方法,一种是直接使用this.$store.state
来获取:
1 2 3 4 5 6 7 8
| export default { computed: { count() { return this.$store.state.count; } } }
|
另一种是使用mapState
辅助函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { mapState } from 'vuex'
export default { computed: mapState({ count: 'count', myStr (state) { return state.str + this.msg } }) }
|
映射的计算属性名与state
子节点名称相同,也可以给mapState
传一个数组:
1 2 3 4
| computed: mapState([ 'count' ])
|
利用对象展开运算符,可以让mapState
与局部的计算属性混合使用:
1 2 3 4 5 6 7 8 9 10
| computed: { count() { return this.$store.state.count; }, ...mapState({ myMessage: function(state) { return state.str + this.msg } }) }
|
Getter
Getter可以认为是store
中的计算属性,它的返回值会根据它的依赖被缓存起来,且只有它的依赖值发生了改变才会被重新计算
Getter的第一个参数事state
对象,第二个参数事其他getters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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) }, count: (state, getters) => { return getters.doneTodos.length } } })
|
获取getter
通过store.getters
对象,可以直接以属性的方式获取:
1 2 3 4 5
| computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }
|
也可以通过方法的形式获取getters
,让getters
返回一个函数,实现给getters
传参:
1 2 3 4 5 6
| getters: { getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } }
|
使用时:
1
| this.$store.getters.getTodoById(2)
|
和state
类似,getters
也有辅助函数,它将store
中的getter
映射到局部计算属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { mapGetters } from 'vuex'
export default { computed: { ...mapGetters([ 'doneTodosCount', 'anotherGetter', ]), ...mapGetters({ doneCount: 'doneTodosCount' }), } }
|
Mutation
更改Vuex中的state
的唯一方法就是提交mutation
,mutation
在store
中注册,key
值就是事件类型,对应的函数就是回调函数,它接受state
作为第一个参数:
1 2 3 4 5 6 7 8 9 10 11
| const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state, payload) { state.coun += payload.amount; } } })
|
mutation
不能直接调用,只能通过store.commit
方法来调用,第一个参数就是要出发的mutation
的事件名,第二个额外的参数是mutation
的载荷,载荷应该是一个对象:
1 2 3 4 5 6 7
| this.$store.commit('increment', { amount: 100 });
this.$store.commit({ type: 'increment', amount: 100, });
|
也可以使用mapMutations
辅助函数将store.commit
映射为组件的methods
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { mapMutations } from 'vuex'
export default { methods: { ...mapMutations([ 'increment', ]), ...mapMutations({ add: 'increment' }) } }
|
使用mutation
时也需要遵循与Vue一样的规则:
state
中初始化所有所需属性
- 不能直接添加新属性,必须使用
Vue.set
方法或者使用新对象替换老对象
要注意的事,Mutation必须是同步函数,原因是为了使devtool能够捕捉前后状态的快照,异步函数则让状态改变试无法变更的
Action
Action是一个架构上的概念,它提交的是mutation
,不直接改变状态,一般用来在action
内部执行异步操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } decrement({ commit }) { setTimeout(() => { commit('increment') }, 1000) } } })
|
它接受一个store
实例具有相同方法和属性的对象作为参数context
,所以可以通过context.commit
来提交mutation
,也可以通过context.state
和contet.getters
来获取state
和getters
可以通过参数解构来简化代码:
1 2 3 4 5
| actions: { increment ({ commit }) { commit('increment') } }
|
Actions通过store.dispatch
来触发:
1 2 3 4 5 6 7
| this.$store.dispatch('increment', { amount: 100 });
this.$store.dispatch({ type: 'increment', amount: 100, });
|
也可以使用mapActions
来将store.dispatch
映射为组件的methos
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { mapActions } from 'vuex'
export default { methods: { ...mapActions([ 'increment', ]), ...mapActions({ add: 'increment' }) } }
|
可以在Action中返回Promise,让store.dispatch.then
继续处理异步流程,也可以将Action进行组合:
1 2 3 4 5 6 7 8 9 10 11
|
actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') commit('gotOtherData', await getOtherData()) } }
|
Module
可以使用modules
选项将Store分割成模块,每个模块都有自己的state
、getter
、mutation
、action
,还可以嵌套子模块
(1)默认情况,未添加命名空间
要注意的是,state
默认是注册在模块下的,而模块内部的action
、mutation
、getter
都是注册在全局命名空间的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const moduleA = { state: { value: 1 }, getters: { value: 2 }, mutations: { increment() {} }, actions: { foo() {} } }
const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } })
store.state.a.value store.state.b
store.getters.value
store.commit('increment');
store.dispatch('foo');
|
在未添加命名空间的模块内部的
getter
,根节点状态(rootState
)和根节点Getters(rootGetters
)会作为第三、四个参数暴露出来,(第一个参数是局部状态对象state
,第二个参数是局部getter
对象)
mutation
,第一个参数是模块的局部状态对象
action
,参数仍然是context
,局部状态通过context.state
暴露,根节点状态通过context.rootState
暴露
(2)添加命名空间
由于模块中的getter
、mutation
和action
都是定义在全局空间下的,很有可能在不同模块中出现重名的现象,导致意料之外的情况发生。
为了解决这个问题,并且为了实现更高的封装度和复用性,可以添加namespaced: true
使其成为带有命名空间的模块,它的所有getter
、action
,mutation
都会自动根据模块注册的路径调整命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const state = { name: 'Ronald', };
const getters = { fullName(state) { return 'C ' + state.name } };
const mutations = { changeName(state, payload) { state.name = payload.name } };
const actions = { changeNameDelay(context, payload) { setTimeout(() => { context.commit('changeName', payload) }, 2000) } };
export default { namespaced: true, state, getters, actions, mutations, }
|
组件中使用的时候,除了state
,getter
、action
,mutation
都会自动根据模块注册的路径调整命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| computed: { name() { return this.$store.state.user.name }, fullName() { return this.$store.getters['user/fullName'] }, ...mapGetters({ fullName: 'user/fullName', }), ..mapGetters('user',['fullName']), },
methods: { ...mapActions({ changeNameDelay: 'user/changeNameDelay' } changeNameHandle() { this.$store.commit('user/changeName', { name: 'Messi'}); this.changeNameDelay({ name: 'Kaka'}) } }
|
在添加了命名空间的模块内部的
getter
,根节点状态(rootState
)和根节点Getters(rootGetters
)会作为第三、四个参数暴露出来,(第一个参数是局部状态对象state
,第二个参数是局部getter
对象)
mutation
,第一个参数是模块的局部状态对象,触发时commit
添加root: true
就可以在全局命名空间内分发Mutation
action
,参数仍然是context
,局部状态通过context.state
暴露,根节点状态通过context.rootState
暴露,根节点的Getter会通过context.rootGetters
暴漏,触发时dispatch
添加root: true
就可以在全局命名空间内分发Action
嵌套模块的情况,如果没有添加namespaced: true
,则会继承父模块的命名空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const store = new Vuex.Store({ modules: { account: { namespaced: true,
state: { ... }, getters: { isAdmin () { ... } }, actions: { login () { ... } }, mutations: { login () { ... } },
modules: { myPage: { state: { ... }, getters: { profile () { ... } } },
posts: { namespaced: true,
state: { ... }, getters: { popular () { ... } } } } } } })
|
在使用mapState
辅助函数绑定命名空间的模块时,可以将模块的空间名称字符串作为第一个参数传递给函数,这样所有绑定都自动将该模块作为上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b }), ...mapState('some.nested.module', { a: state => state.a, b: state => state.b }) }, methods: { ...mapActions([ 'some/nested/module/foo', 'some/nested/module/bar' ]), ...mapActions('some/nested/module', [ 'foo', 'bar', ]), }
|
可以对上面的辅助函数的使用再进一步优化,通过使用createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数,就无须再为辅助函数逐个添加命名空间了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default { computed: { ...mapState({ a: state => state.a, b: state => state.b }) }, methods: { ...mapActions([ 'foo', 'bar' ]) }
|
当需要创建一个模块的多个实例时,需要使用一个函数来声明模块状态(与Vue组件内的data
出于同样的原因和解决方法)
1 2 3 4 5 6 7 8
| const MyReusableModule = { state () { return { foo: 'bar' } }, }
|