前端学习日记 # Vuex

278 阅读4分钟

简介

专门为Vue单页应用实现集中式状态(数据)管理的一个Vue插件

作用

对Vue应用中多个组件的共享状态(数据)进行集中式管理(读/写),也是一种组件间的通信方式,适用于任意组件间的通信

什么时候使用

多个组件共享同一个状态(数据),最起码都要有2个组件以上共有这个数据才使用Vuex

使用

安装

npm i vuex

引入并使用

import Vuex from 'vuex'

Vue.use(Vuex)

执行上面代码后,vm、vc对象上就能挂载一个属性 $store

创建 store 对象

src/plugins/ 下创建 store.js 文件,代码如下

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 准备actions对象 —— 响应组件中用户的动作
const actions = {}
// 准备mutations对象 —— 修改state中的数据
const mutations = {}
// 准备state对象 —— 用于存储数据
const state = {}

// 创建并暴露store对象
export default new Vuex.Store({
    actions,
    mutations,
    state
})

在 main.js 中配置 store 项(关联)

import Vue from 'vue'
import store from '@/plugins/store'

new Vue({
    store,
    el: '#app'
})

安装完成后,所有vm、vc都会新增一个共有属性:store原理就是通过Vue.prototype.store 原理就是通过`Vue.prototype.store`添加

工作原理

image.png

从图上可以看出,Vuex最核心、最必备的3大组成部分分别是:Actions、Mutations、State

Actions负责与后端进行通信获取数据和业务处理
Mutations负责简单的数据更改,也是VueDevTools工具监视的位置

必备配置项(3种)

Actions

与 Mutation 类似,唯一区别是:
mutation 是同步函数,唯一修改 state 状态值的方法
action 是异步函数,但不能修改 state 状态值

定义函数

// 定义的函数接受2个参数:
1、context
2、传参

actions: {
  incrementAsync (context, params) {
    setTimeout(() => {
      context.commit('increment', params)
    }, 1000)
  }
}

组件中使用

// 第一种方式
this.$store.dispatch('incrementAsync', 12);

// 第二种方式 (当成方法使用,更加简单方便)
1、从 vuex 中按需导入 mapMutation 辅助函数
import { mapActions } from vuex
2、将导入的 action 函数,映射为当前组件的methods函数
methods: {
    ...mapActions(['incrementAsync'])
}
3、调用
this.incrementAsync(参数);

模块化时,该如何使用呢?

// 第一种方式
this.$store.dispatch('base/incrementAsync', 12);

// 第二种方式
1、从 vuex 中按需导入 mapMutation 辅助函数
import {mapActions} from vuex
2、将导入的 action 函数,映射为当前组件的methods函数
methods: {
    ...mapActions(['base/incrementAsync']),
    ...mapActions('base', ['incrementAsync'])
}
3、调用
this['base/incrementAsync'](4)
this.incrementAsync(13)

Mutations

vuex 不允许直接修改 state 中数据,必须使用 Mutation 来修改
Mutation 中定义的函数必须是同步函数,不能是异步的

声明函数

// 定义的函数接受2个参数:
1、state
2、传参

mutations: {
  increment (state, params) {
     // 变更状态
     state.count++
  }
}

组件中使用

// 第一种方式
this.$store.commit('increment', 12);

// 第二种方式 (当成方法使用,更加简单方便)
1、从 vuex 中按需导入 mapMutation 辅助函数
import {mapMutations} from vuex
2、将导入的 mutation 函数,映射为当前组件的methods函数
methods: {
    ...mapMutations(['increment'])
}
3、调用
this.increment(参数);

模块化时,该如何使用呢?

// 第一种方式
this.$store.commit('base/increment', 12);

// 第二种方式
1、从 vuex 中按需导入 mapMutation 辅助函数
import {mapMutations} from vuex
2、将导入的 mutation 函数,映射为当前组件的methods函数
methods: {
    ...mapMutations(['base/increment']),
    ...mapMutations('base', ['increment'])
}
3、调用
this['base/increment'](4)
this.increment(13)

State

用于存储所有共享数据
组件使用时,要在 computed 声明

与data一样,都实现了数据代理,都是响应式的数据(数据变,依赖数据的页面也发生变化)

第一种、直接访问(不推荐)

// 非模块
this.$store.state.全局数据名称

// 模块
this.$store.state.模块名.全局数据名称

第二种、使用辅助函数(推荐)

1、从 vuex 按需导入 mapState 辅助函数
import {mapState} from 'vuex'

2、将当前组件需要的全局数据,映射为当前组件的computed计算属性
// 非模块写法
computed: {
    ...mapState(['count'])
}
或者
computed: {
    ...mapState({
         count: state => state.count,  // 第一种
         count: 'count'  // 第二种
    })
}

// 模块
computed: {
    ...mapState({
         count: state => state.模块名.count
    })
}

-注意-

官方推荐我们将vuex的state属性绑定到computed,为何不可以绑定到data里面呢?

错觉:data中的内容只会在 created 钩子触发前初始化一次,之后需要通过js修改data里面的属性值,页面才响应式改变,那么把state里的数据绑定到data中,state中数据改变,data中对应的数据也会跟着改变不对吗?

回答:上面说法有一半对,一半错。到底错在哪呢?

错在:如果state里面的状态值类型是数值、布尔、字符串基础数据类型的话,那么赋值就是值传递,如下

let a = 12;
let b = a;
a = 20;

alert(a) //20
alert(b) //12

这样你明白了吧,如果把state中状态传递给data,那么当state中状态改变后,因为不是引用传递,data中对应属性值不会改变

总结:
data是在create钩子触发前初始化一次
data中由于没有依赖跟踪,所以必须引用传递才可以做到响应式

选配配置项

Getters

用于对 state 中数据进行加工,不会对 state 中原数据有任何修改
类似于 computed 计算属性,要复用可以使用 getters
state 中数据发生变化, getter 中数据也发生变化 —— 响应式
组件使用时,要在 computed 声明

配置

在Store对象创建时添加配置项getters

const getters = {
    bigSum(state){
        return state.sum*100-20;
    }
}

export default new Vuex.Store({
    ......
    getters
})
使用
// 第一种方式 (不推荐)
this.$store.getters.名称

// 第二种方式 (优化写法,推荐)
1、从 vuex 中按需导入 mapGetters 辅助函数
import {mapGetters} from vuex
2、将导入的 getter 函数,映射为当前组件的 computed 计算属性
computed: {
    ...mapGetters(['bigSum'])
}
3、模板中使用
<div>{{ bigSum }}</div>

Modules 模块化 (重点)

Vue单页应用必备的技术,为防止代码过于冗重,我们必须使用模块化思想,把代码分门别类拆分,便于维护

拆分模块

根据业务功能拆分模块,模块如下定义:

// 创建文件定义模块a
export default {
    namespaced: true, // 必须开启模块命名空间 
    state: {
        name: 'xxx',
        title: 'ssss',
        sum: 0
    },
    actions: {
        getUpdata(context, value){}
    },
    mutations: {
        updateSum(state, value){}
    },
    getters: {
        bigSum(state){
            return state.name*10;
        }
    }
}

配置模块

// 导入模块
import xxx from './a'
import aaa from './b'

export default new Vuex.Store({
    modules: {
        xxx,
        aaa
    }
})

使用

非辅助函数使用
// state
{{$store.state.模块名.属性名}}
this.$store.state.模块命名空间名.属性名

// getters
{{$store.getters['模块名/属性名']}}
this.$store.getters['模块名/属性名']  // 只能这样写

// mutations
this.$store.commit('模块命名空间名/函数名', 传参)

// actions
this.$store.dispatch('模块命名空间名/函数名', 传参)
辅助函数使用

computed: {
    // state
    ...mapState(命名空间名,{使用名1:'xxx', 使用名2: 'aaa'})
    ...mapState(命名空间名,['xxx', 'aaa'])
    
    // getters
    ...mapGetters(命名空间名,{使用名1:'xxx', 使用名2: 'aaa'})
    ...mapGetters(命名空间名,['xxx', 'aaa'])
    
    // mutations
    ...mapMutations(命名空间名,{函数名1:'xxx', 函数名1: 'aaa'})
    ...mapMutations(命名空间名,['xxx', 'aaa'])
    
    // actions
    ...mapActions(命名空间名,{函数名1:'xxx', 函数名1: 'aaa'})
    ...mapActions(命名空间名,['xxx', 'aaa'])
}

代码生成器(简化代码)

Vuex为了开发者更加方便使用Vuex中存储的状态(数据),给开发者提供了4个辅助函数,用于生成计算属性代码

mapState

借助 mapState 函数生成计算属性代码,用于帮我们映射 State 中的数据为计算属性

对象写法
import {mapState} from 'vuex'

export default {
    name: 'book',
    data(){
        return {}
    },
    computed: {
        ...mapState({he: 'sum', bigSum: 'bigSum'})
    }
}

// 模板中使用
<p>{{he}}</p>

这种写法最大的好处是,当你遇到data或computed中原本的变量名与vuex中状态的变量发生命名冲突时,可以使用此写法更换一个名称,避免命名冲突

数组写法

确定不发生变量命名冲突时,直接使用原本vuex中数据名称,就可以使用此写法

import {mapState} from 'vuex'

export default {
    name: 'book',
    data(){
        return {}
    },
    computed: {
        ...mapState(['sum', 'bigSum'])
    }
}

// 模板中使用
<p>{{sum}}</p>

mapGetters

借助 mapGetters 函数生成计算属性代码,用于帮我们映射 Getters 中的数据为计算属性

和 mapState 用法一样

对象写法
import {mapGetters} from 'vuex'

export default {
    name: 'book',
    data(){
        return {}
    },
    computed: {
        ...mapGetters({he: 'sum', bigSum: 'bigSum'})
    }
}

// 模板中使用
<p>{{he}}</p>
数组写法
import {mapGetters} from 'vuex'

export default {
    name: 'book',
    data(){
        return {}
    },
    computed: {
        ...mapGetters(['sum', 'bigSum'])
    }
}

// 模板中使用
<p>{{sum}}</p>

mapMutations

借助 mapMutations 生成对应的方法代码,用于帮我们生成与Mutations 对话的方法,即:包含$store.commit()函数

使用mapMutations前
export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        updateCommonData(){
            this.$store.commit('goTest', this.n);
        }
    }
}

每次调用Mutations中的方法都要写一段冗余的代码this.$store.commit('xxxx', 参数),Vuex为了解决这个问题,提供给开发者一个辅助生成代码函数mapMutations

使用mapMutations后
export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        ...mapMutations({updataCommonData: 'goTest'})
    }
}

...mapMutations({updataCommonData: 'goTest'}) 最终生成代码如何?

updateCommonData(value){
    this.$store.commit('goTest', value);
}
对象写法

唯一好处就是能够更改方法名称,解决命名冲突

export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        ...mapMutations({updataCommonData: 'goTest'})
    }
}
数组写法

唯一好处就是够简单

export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        ...mapMutations(['updataCommonData'])
    }
}

mapActions

借助 mapActions 生成对应的方法代码,用于帮我们生成与mapActions 对话的方法,即:包含$store.dispatch()函数

对象写法

唯一好处就是能够更改方法名称,解决命名冲突

export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        ...mapActions({updataCommonData: 'goTest'})
    }
}
数组写法

唯一好处就是够简单

export default {
    name: 'book',
    data(){
        return {
            n: 1
        }
    },
    methods: {
        ...mapActions(['updataCommonData'])
    }
}