Vuex简介
Vuex是一款状态管理的Vue插件。插件注册之后会向Vue的原型添加一个$store属性用来存储Vuex实例,组件内容可以通过该属性来访问Vuex中的state,或通过响应的方法修改state中的属性。
Vuex执行流程
官网中Vuex的执行流程很清晰的表达了Vuex的状态流转过程:
- 通过差值表达式或指令将state绑定到视图Vue Component上。
- 要想改变 state 中的状态需要通过触发事件来提交Mutations中改变state的方法的请求。
- 异步任务需要在Actions中的方法中定义,然后在方法中通过提交Mutations中改变state的方法的请求来改变state。Actions不能直接改变state。
Vuex核心概念
-
state
单一状态树,用来存储Vuex管理的状态。是一个响应式的对象,state中的数据改变,绑定的视图也会自动更新。
定义:state: { count: 0, msg: 'Hello Vuex' },
在视图中绑定state有两种方法:
- 通过$store.state来绑定
<div id="app"> <h1>count: {{$store.state.count}}</h1> <h1>msg: {{$store.state.msg}}</h1> </div>
- 通过mapState方法将要绑定的state状态映射到computed计算属性中,然后像使用组件的计算属性一样使用它
mapState接收一个数组或对象,返回一个对象,数组的每一项对应返回对象的属性。<template> <div id="app"> <h1>State:</h1> count: {{count}} msg: {{msg}} </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { computed: { ...mapState(['count', 'msg']), // ...mapState({num: 'count', message: 'msg'}), // key为映射后的计算属性,val是state中的状态属性 } } </script>
- 通过$store.state来绑定
-
Getters
类似计算属性,适用于要将state中的数据进行简单处理,然后返回。
定义:getters: { reverseMsg(state) { return state.msg.split('').reverse().join('') } },
在组件内使用Getter也有两种方法:
- 通过$store.getters绑定
<p>ReverseMsg: {{$store.getters.reverseMsg}}</p>
- 通过mapGetters方法映射到computed计算属性中,然后像使用组件的计算属性一样使用它。mapGetters的使用和mapState类似
<template> <div id="app"> <h1>Getters:</h1> <p>ReverseMsg: {{$store.getters.reverseMsg}}</p> <!-- <p>ReverseMsg: {{rvm}}</p> --> </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { computed: { ...mapGetters(['reverseMsg']) // ...mapGetters({ rvm: 'reverseMsg'}), } } </script>
- 通过$store.getters绑定
-
Mutations
更改state中状态的唯一方法是提交mutation,mutation必须是同步执行的。
定义:mutations: { increment(state, payload) { state.count += payload } },
mutation的提交也有两种方法:
- 通过$store.commit(‘type’, payload)提交
<button @click="$store.commit('increment', 1)">mutation</button>
- 通过mapMutaions()方法将mutation映射到methods中。
通过mapMutations方法生成的方法等价于上面注释的方法。<template> <div id="app"> <h1>Mutation:</h1> <button @click="$store.commit('increment', 1)">mutation</button> <!-- <button @click="increment(1)">mutation</button> --> </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { methods: { ...mapMutations(['increment']), // increment(val) { // 上面等价于 // this.$store.commit('increment', val) // } } } </script>
- 通过$store.commit(‘type’, payload)提交
-
Actions
执行异步修改state的方法,但具体的执行过程还需要commit mutation来完成。
定义:actions: { asyncIncrement(content, payload) { setTimeout(() => { content.commit('increment', payload) }, 3000) } },
actions的使用的两种方法:
- 通过$store.dispatch派发
<button @click="$store.dispatch('asyncIncrement', 5)">action</button>
- 通过mapActions将action映射到methods中
<template> <div id="app"> <h1>Actions:</h1> <button @click="$store.dispatch('asyncIncrement', 5)">action</button> <!-- <button @click="asyncIncrement(5)">action</button> --> </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { methods: { ...mapActions(['asyncIncrement']) } } </script>
- 通过$store.dispatch派发
-
Modules
将单一状态树拆分成多个模块,可以定义自己的state,mutations,actions,getters以及嵌套的modules。子模块中的state会合并store的state中:访问时通过$store.state.[moduleName].[state]来访问子模块中的state。
子模块中的mutations、actions,getters会合并到state中的mutations、actions,getters中。mutations、actions中的属性将会变成数组,保存所有的同名的属性,当commit时,对应数组内容方法都会执行。getters则还是对象,根模块中的getters和子模块的getters不能有重名。
定义:const products = { namespaced: true, state: { list: [ { id: 1, name: 'Product A' }, { id: 2, name: 'Product B' } ], nums: 0, }, mutations: { reset(state) { state.list = [] }, increment(state, payload) { state.nums += payload; } }, actions: {}, getters: { sumWithRoot(state, getters, rootState) { return state.nums * rootState.count; } } } export default new Vuex.Store({ modules: { products, } })
使用:和根模块的使用类似,通过$store或通过mapXXX方法映射到组件内容的状态和方法。
<template> <div id="app"> <h1>Modules:</h1> <!-- <p>Product Nums: {{$store.state.products.nums}}</p> <p>Product Nums Width Root: {{sumWithRoot}}</p> <p>{{$store.state.products.list}}</p> <button @click="$store.commit('reset')">reset</button> --> <p>Product Nums: {{$store.state.products.nums}}</p> <button @click="$store.commit('products/increment', 3)">product nums increment</button> <!-- <p>Product Nums Width Root: {{sumWithRoot}}</p> --> <p>{{$store.state.products.list}}</p> <button @click="$store.commit('products/reset')">reset</button> </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { computed: { ...mapGetters('products', ['sumWithRoot']) }, } </script>
子模块默认不开启命名空间,如果想要开启命名空间需要在子模块定义添加namespaced属性并设置为true即可。
开启命名空间之后,store中的getters,mutations,actions中的属性都将拼接上子模块名。map 函数的第一个参数可以传入命名空间的名字。
Vuex插件
Vuex 插件就是一个函数,它接收 store 作为唯一参数,函数中可以注册函数,在让它在所有mutation之后执行。
插件应该在new Vue.Store之前定义。
在插件中不允许直接修改状态——类似于组件,只能通过提交 mutation 来触发变化。
// vuex 插件
const saveLocalStorage = store => {
store.subscribe((mutations, state) => {
if (mutations.type.startsWith('carts/')) {
window.localStorage.setItem(
'carts',
JSON.stringify(state.carts.cartProducts)
)
}
})
}
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
products,
carts
},
plugins: [saveLocalStorage]
})