接
Vue笔记
五、Vuex —— 在应用很复杂时用此保管数据
5.1、理解Vuex
5.1.1、概念
专门在Vue中实现集中式状态(或数据)管理的一个 Vue 插件,对Vue应用中多个组件的共享状态进行集中式管理(读 / 写),也是一种组件间通信的方式,且适用于任意组件间的通信。
5.1.2、什么时候使用?
- 多个组件依赖于同一状态
- 来自不同组价的行为需要变更同一状态
5.1.3、原理
5.1.4、安装
vue2使用vuex3版本
npm i vuex@3
vue3使用vuex4版本
npm i vuex@4
5.1.5、引入和使用
main.js
//引入
import Vuex from 'vuex'
//使用
Vue.use(Vuex)
自此创建vm时,就可以创建store配置项
vuex的代码一般放在一个专门的文件夹里,官网建议其文件夹名为
store,store的代码放在文件index.js中
src/store/index.js:此处引入Vue,创建、引入Vuex插件并应用
//改文件用于创建Vuex中最为核心的store
//引入Vue
import Vue from "vue"
//引入Vuex
import Vuex from "vuex"
//应用Vuex
Vue.use(Vuex)
//准备actions,用于响应组件中的动作
const actions = {}
//准备mutations,用于操作数据(state)
const mutations = {}
//准备state,用于存储数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
//注意重名可以使用简写形式
actions: actions,//配置
mutations: mutations,//配置
state: state,//初始化数据
getter:getter
})
main.js,创建vm时引入store配置项
import Vue from 'vue'
import App from './App.vue'
import vueResource from 'vue-resource'
import store from './store'
Vue.config.productionTip = false
Vue.use(vueResource)
new Vue({
el: '#app',
render: h => h(App),
store,//使用 store: store, 的简写形式
beforeRender(){
Vue.prototype.$bus = this;
}
})
之后vc和vm就可以使用$store
5.1.6、store
store是一个容器,包含着应用中大部分的state,官方文档中说明,Vuex与单纯的全局对象有两点不同
- Vuex的状态存储是响应式。Vue组件从
store中读取状态时,如果store的状态发生改变,那么响应组件也会迅速更新 store中的状态无法直接改变,只能通过显式地提交(commit)mutation来改变
创建一个简单的store
const store = new Vue.Store({
state:{
cnt: 0
},
mutations:{
increment(state){//传入 state 对象作为参数
state.cnt++
//通过 state 调用 cnt
}
}
})
获取状态对象:store.state.xxx,触发状态变更:store.commit('xxx')
在Vue组件中访问store原型
this.$store.state.xxx;
this.$store.commit('xxx');
---
组件中调用store中的状态只需要在计算属性中返回即可,触发变化只需要在组件的methods中提交mutation
5.2、Vuex核心概念和API
state、getters、mutations、actions、modules 都放在store里面
5.2.1、state
state在Vuex中用于存储数据,且存储在Vuex中的数据与存储在Vue实例中data上的数据遵循相同的规则,这种方式会及时触发相关联的 DOM 更新
const state - {
sum:0,
subject:'math'
}
在根组件App.vue注册store,由此将其“注入”到所有子组件中,在子组件中通过this.$store访问store原型,再通过计算属性computed返回某个状态来读取store实例中状态
const vc = {
template: `<h1>{{number}}</h1>`,
computed: {
number(){
return this.$store.state.num
}
}
}
这种方式比在每个组件中都注册一次各自的store再使用要简单得多
mapState辅助函数
帮助映射
state中的数据为计算属性
- 作用:协助生成计算属性,简化声明过程,使用前需要从
vuex中引入 - 使用情景:一个组件需要多个状态,并要将这些状态都声明为计算属性时
- 格式:
const x = mapState({keyName_01:'value_01',keyName_02:'value_02'}),参数传入一个对象,对象中是键值对,值必须用'',再用...对象展开运算符将此mapState放入计算属性computed中
对象写法
import {mapState} from 'vuex'
export default {
//省略
computed:{
//使用展开运算符将 mapState 在计算属性中展开
//借助 mapState 生成计算属性,从 state 中读取数据 ---对象写法
...mapState({he:'sum',subject:'math'}),
//也可以 ...x
},
mounted(){
const x = mapState({he:'sum',subject:'math'})
//sum必须用'',he可以不用,解析的时候会加上
}
}
mapState({xxx})返回一个对象
字符串数组写法
当键值名相同且其在state中存在时,还可以使用简写方式,但应当用''
state
const state = {
sum:0,
subject:'math'
}
mapState()
import {mapState} from 'vuex'
export default {
//省略
computed:{
...mapState(['sum','subject']), //----字符串数组写法
}
}
//混入 computed 后展开相当于于:
/*
* sum(){
* return this.$store.state.sum //0
* },
* subject(){
* return this.$store.state.subject //'math'
* }
*/
5.2.2、getters
作用:加工stata中的数据,注意,这没有改变其本身的值
const getters = {
getRes(state){
return state.sum+50
}
}
mapGetters
与 mapState类似,使用前也要引入,但其映射的是getters中的数据为计算属性
5.2.3、mutations
更改Vuex的store中state状态的唯一方法是提交mutation,其中包含一个个字符串的 事件类型type 和对应的回调函数,通过回调函数对state中的数据进行更改,回调函数接收的第一个参数是state
const store = new Vuex.Store({
state:{
number: 13
},
mutations:{
reduce(state){//第一个参数是 state, 事件类型名是 reduce
//通过 state 调用其中的数据并修改
state.number--
}
}
})
使用store.commit()方法调用相应的type对state中的状态进行修改,注意type的名字是是字符串形式
store.commit('reduce')
还可以向事件类型中传入额外的形参,相应的,向store.commit()中也要传入对应类型的实参,这个参数可以是一个String或Number型变量,但更多时候最好是一个对象,这个额外的参数又叫载荷Payload
const store = new Vuex.Store({
state:{
number: 13
},
mutations:{
reduce(state, obj){//将整个对象作为载荷
state.number += obj.num1
}
}
})
//向对应的commit传参
store.commit('reduce',{//将事件类型名作为第一个实参
num1: 100
//因为 reduce 中传入的形参是对象,所以这里实参也该是对象
})
提交
mutation的另一种方式:直接使用包含type属性的对象
store.commit({//传入一个对象型实参
type: 'reduce',//将事件类型名放到该对象的 type 属性中
num1: 66
})
在对象上添加新属性
- 使用
set方法:Vue.set(obj, 'newStr', 123, ...) - 使用解构赋值:
state.obj = {...state.obj02, 'str', 123}
可以使用常量替代事件类型
mutation.js
export const getSum = 'getSum'
store.js
import Vuex from 'vuex'
import {getSum} from './mutation.js'
const store = new Vuex.Store({
state:{
num: 10
},
mutation:{
//用一个常量作为函数名
[getSum](state, n){
state.num += n
}
}
})
mutation必须是同步函数
mapMutations辅助函数
用于在组件中提交mutation时,将组件中的 methods 映射为store.commit,这种写法需要提前在根组件注入store
import {mapMutations} from 'vuex'
export default{
//省略
methods:{
...mapMutations([
'reduce',//将 this.reduce() 映射为 this.$store.commit('reduce')
//支持传入载荷
'increase',//假设有个载荷为 num, 即原本为 this.increase(num)
//将其映射为: this.$store.commit('increase', num)
])
}
}
5.2.4、actions
Action 类似于 Mutation,但有区别,主要是下面两点
- actions 用于提交 mutation, 而非像mutation那样变更状态
- action 可以包含任意异步操作,而mutation只能包含同步操作
actions函数接受一个context对象作为参数,这个对象与store实例具有相同的属性和方法,但不是store实例本身。可以通过context对象来调用commit提交mutation等
action:{
reduce(context){
context.commit('reduce')
}
}
需要多次使用某方法时,也可以使用参数解构来简化代码
actions:{
reduce( {commit} ){
commit('reduce')
}
}
分发Action
触发action需要使用store.dispatch('xxx'),它返回一个Promise,actions可以用载荷或者对象方式进行分发
//载荷方式
store.dispatch('reduce',{
num: 30
})
//对象方式
store.dispatch({
type:'reduce',
num: 30
})
在actions内进行异步操作
actions:{
reduceAsync( {commit} ){
setTimeout(()=>{
commit('reduce')
}, 2000)
}
}
组件中分发Action
- 使用
this.$store.dispatch('xxx')分发 - 使用
mapActions函数将组件的 methods 映射为store.dispatch调用
import { mapActions } from 'vuex'
export default{
...mapActions(['reduce', 'incrementBy']),//mapActions同样支持载荷
...mapActions({
add:'increment'//将 this.add() 映射为 this.$store.dispatch('increment')
})
}
组合Action
当一个
store.dispatch在不同模块中触发的全部action函数都完成后,它自己返回的Promise才会执行
可以通过组合多个action,来处理复杂的异步流程。这之前,需要了解到: store.dispatch()可以用来处理被触发的 action 的处理函数返回的Promise,并且它自己也返回Promise,比如
store.dispatch('actionA').then(()=>{
commmit('reduce')
})
使用async组合多个action实现异步流程
action:{
async actionA ( {commit} ){
commit('fn_1', await fn_1())
},
async actionB ( {dispatch,commit} ){
await dispatch('actionA');//actionA完成后再继续
commit('fn_2', await fn_2())
}
}
5.2.5、modules
如直观的意义一样。modules就是将store分割为数个模块,每个模块都有各自的state、mutation、action、getter,同时每个模块还能继续向下分隔。
但值得注意的是,modules的声明应当在store之前。
const moduleA = {
state: () => ({
//...
}),
mutations: {
//...
},
actions: {
//...
},
getters: {
//..
}
}
const moduleB = {
state: () => ({
//...
}),
mutations: {
//...
},
actions: {
//...
},
getters: {
//..
}
}
const store = new Vue.Store({
mudules: {
firModule: moduleA,
secModule: moduleB
}
})
//使用模块:
console.log(store.state.firModule);//输出 firModuleA 的 state
模块的局部状态
1、在模块内部的mutation和getter中,接收的首个参数state是其所在模块内的局部状态对象
const moduleA = {
state:()=>({
number: 30
}),
mutations: {
reduce(state){//此处的 state 是这个模块内的 state
state.number++
}
}
}
2、模块内的action,局部状态通过context.state暴露,其根节点状态通过context.rootState暴露
const moduleA = {
actions:{
isSame( {state, commit, rootState} ){
if((state.number + rootState.num)%2 === 1){
commit('reduce')
}
}
}
}
3、模块内部的getter,根节点状态rootState作为第三个参数暴露
const moduleA = {
getters:{
getSum (state, getter, rootState){
return state.number + rootState.num
}
}
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间,这使多个模块能够对同一 mutation 或者 action 做出响应。
如果希望某个模块具有高度的封装度且更加方便被复用,那么可以添加namespaced: true,使其成为带命名空间的模块
const store = new Vuex.Store({
modules: {
moduleA: {
namespaced: true,//这个模块被命名为moduleA, 需要使用 moduleA 对其调用
state: {
num1: 10
},
getter: {...},
actions: {...},
mutations: {...},
//在一个模块中嵌套子模块,继承父模块命名空间
modules: {
//嵌套的模块不设置namespaced,则会自动继承父模块的命名空间
//换而言之,不设置namespaced的子模块在父模块的命名空间内
moduleB: {
state: {
num2: 50 //组件中获取: this.$store.state.moduleA.num2
}
},
//嵌套子模块,不继承父模块命名空间
moduleC: {
namespaced: true,
state:{...},
getters: {
fn(){...} //组件中获取: this.$store.getters['moduleA/moduleC/fn']
}
}
}
}
}
})
在带命名空间的模块内访问全局内容
1、想要使用全局的 state 和 getter, 则需要将rootState作为第三参数,rootGetters作为第四参数传入getter,也会通过context对象的属性传入action
modules:{
A: {//该模块命名空间为A
namespaced: true,
getters: {
//该A模块的 getter 中,getters被局部化,只属于这个这个A模块
theGetter(state, gettersm rootState, rootGetters){
//通过模块A访问getState
getters.getState; //对应路径: 'A/getState'
//通过根节点访问getState
rootGetters.getState //对应路径: 'getState'
},
getState:state => {...}
}
}
}
2、需要在全局命名空间内分发 action 或者提交 mutation 时,需要将对象{root: true}作为第三个参数传给dispatch或commit
modules: {
A: {
namespaced: true,
actions: {
//在带命名空间的模块中, dispatch 和 commit 均被局部化
//可以使用 root 属性访问 根dispatch 和 根commit
theAction ( {dispatch, commit, getters, rootGetters} ){
dispatch('otherAction', null, {root: true}) //对应路径: 'otherAction'
commit('otherMutation', null, {root: true}) //对应路径: 'otherMutation'
}
},
otherAction(context, payLoad){...}
}
}
在带命名空间的模块内注册全局action
需要添加{root: true},并将这个需要注册的 action 的定义放在 handler内
const moduleA = {
namespaced: true,
theAction: {
root: true, //条件1:添加{root: true}
handler(namespacedContext, payLoad){ //条件2:将要注册的theAction放入handler
console.log(namespacedContext);//上下午信息
console.log(payLoad)
}
}
}
带命名空间的绑定函数
用于简化函数mapState、mapGetters、mapActions、mapMutations绑定带命名空间模块的操作
方法:将模块的空间名称字符串作为第一个参数传递给以上函数,由此,所有绑定都会自动将该模块作为上下文
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
此外,还可以使用createNamespacedHelpers创建基于某个命名空间辅助函数,它会返回一个对象,其中有新的绑定在给定命名空间值上的组件绑定辅助函数
//首先引入createNamespacedHelpers
import {createNamespacedHelpers} from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
模块动态注册
创建store后,可以使用store.registerModule方法进行注册模块,这个方法接受3个参数,第一个参数是模块名,第二个参数是模块的内容。
如果不注册嵌套模块,则第一个参数直接传字符串形式的模块名;如果注册嵌套模块,则第一个参数传入一个数组,其中依次存放父模块名、子模块名
import Vuex from 'vuex'
//创建store
const store = new Vue.Store({...})
//动态注册非嵌套模块
store.registerModule('moduleName',{
//...
})
//注册嵌套模块
store.registerModule(['fatherModule', 'childModule'],{
//...
})
访问状态:
//访问非嵌套模块
store.state.moduleName
//访问嵌套模块
store.state.fatherModule.childModule
模块动态卸载:
store.unregisterModule(moduleName)
检测模块是否注册成功
store.hasModule(moduleName)
保留state
store.registerModule()接收的第三个参数,是一个对象,可以在其中设置perserveState:true,从而在注册信模块时,保留过去的state——即这个模块的state不会和其action、mutation、getter一起被添加到store里
模块重用
需要创建一个模块的多个实例时,通过使用模块重用的方法来解决可能出现的状态对象被修改时,store或模块间数据相互污染的问题
解决方式是:使用一个函数来声明模块的状态(vue2.3.0及以上支持)
const moduleA = {
state: ()=> ({
//...
}),
//mutation、action、getter等同理
}