9、vuex

vuex是什么

概念:专门在vue中实现集中式状态管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读、写)也是一种组件间通信的方式,且适合于任意组件间通信。

https://github.com/vuejs/vuex

image-20220420210808376

什么时候是用vuex

1、多个组件依赖于同一个状态

2、来自不同组件的行为要变更同一状态

工作原理

image-20220420213045697

搭建vuex环境

1、安装vuex

注意vuex@4版本只能在vue3中使用,如果vue2中使用,需要安装vuex@3

cnpm i vuex@3 

3、src下创建store/index.js,声明store配置项actions、mutations、state,并且Vue使用vuex(必须在store实例化之前,所以不能写在main.js),然后实例化store并导出

//该文件用于创建vuex中核心store
import Vue from 'vue';
import Vuex from 'vuex'
//使用
Vue.use(Vuex);

//用于响应组件的动作,处理业务逻辑,比如请求接口
const actions = {

}

//用于操作数据
const mutations = {

}

//用于存储数据
const state = {

}

//创建并导出store
export default new Vuex.Store({
    actions,
    mutations,
    state
});

4、main.js中引入store,并声明为Vue实例配置项

import store from './store';

new Vue({
    render: h => h(App),
    store
}).$mount("#app")

基本使用

store/index.js定义多组件共享数据,并且定义操作数据的方法

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {
    add(context,val){
        //操作业务逻辑
        val += 1;
        //调用mutations中的add
        context.commit("add",val);
    }
}

const mutations = {
    add(state,val){
        //对数据进行操作
        state.num += val;
    }
}

const state = {
    num: 0
}

export default new Vuex.Store({
    actions,
    mutations,
    state
});

App.vue展示数据,引入两个操作数据的组件

<template>
  <div>
    <!-- 获取state中的num值 -->
    {{$store.state.num}}
    <Comp1/>
    <Comp2/>
  </div>
</template>

<script>
import Comp1 from './components/Comp1.vue';
import Comp2 from './components/Comp2.vue';
export default {
    name: "App",
    components: {
      Comp1,
      Comp2
    }
}
</script>

Comp1.vue调用$store.dispatch,对数据进行业务逻辑操作(actions),然后再更改数据(mutations)

<template>
    <button @click="add2">add2</button>
</template>
<script>
export default {
    name: "Comp1",
    methods: {
        add2(){
            //dispatch调用的是action中add,后面的参数为val值
            this.$store.dispatch("add", 1);
        }
    }
}
</script><template>
    <button @click="add2">add2</button>
</template>
<script>
export default {
    name: "Comp1",
    methods: {
        add2(){
            //dispatch调用的是action中add,后面的参数为val值
            this.$store.dispatch("add", 1);
        }
    }
}
</script>

Comp2.vue调用$store.commit直接对数据进行操作(mutations),不需要业务逻辑操作

<template>
    <button @click="add1">add1</button>
</template>
<script>
export default {
    name: "Comp2",
    methods: {
        add1(){
            //commit直接调用mutations中的add函数,跳过action
            this.$store.commit("add",1);
        }
    }
}
</script>

getters配置项

类似于组件中声明的计算属性computed,用于计算state中的属性

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {}

const mutations = {}

const state = {
    num: 9
}

const getters = {
    tenfold(state){
       	return state.num * 10;
    }
}

export default new Vuex.Store({
    actions,
    mutations,
    state
});
<template>
  <div>
    <!-- 获取 -->
    {{$store.getters.tenfold}}
  </div>
</template>

<script>
export default {
    name: "App"
}
</script>

生成组件配置

在组件里面,我们一直使用this.$store.state.属性来获取属性或this.$store.getters.函数名来获取计算属性结果,过于麻烦,所以vuex提供了一种map...来帮助快速生成计算属性

mapState

用于将state中的属性,在组件中声明为计算属性computed的配置,简化编码

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {}

const mutations = {}
// 声明两个属性
const state = {
    id: "123",
    name: "lucy"
}

const getters = {}

export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
});
<template>
  <div>
    <!-- 直接使用计算属性即可 -->
    <!-- id : {{userId}}, name : {{userName}} -->
    id : {{id}}, name : {{name}}
  </div>
</template>

<script>
//引入mapState
import {mapState} from 'vuex';
export default {
    name: "App",
    computed: {
      // 1、对象写法,用于需要给state中的属性重新起名
      // ...mapState({userId:'id',userName:'name'})
      // 对象写法的属性值也可以是一个函数,例如,引入state中conf中的timer
      // (非常有用)例如需要在方法中获取该对象的m
      // ...mapState({'timer':(state)=>state.conf.timer}),
      // 2、数组写法,属性名就是state中的属性名
      ...mapState(['id','name'])
    }
}
</script>

mapGetters

用于将getters中的属性,在组件中声明为计算属性computed的配置,简化编码

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {}

const mutations = {}

const state = {
    num: 9
}

const getters = {
    tenfold(state){
        return state.num * 10;
    }
}

export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
});
<template>
  <div>
    <!-- 直接使用计算属性即可 -->
    <!-- {{multi10}} -->
    {{tenfold}}
  </div>
</template>

<script>
//引入mapState
import {mapGetters} from 'vuex';
export default {
    name: "App",
    computed: {
      // 1、对象写法,用于需要给getters中的属性重新起名
      // ...mapGetters({multi10:'tenfold'})
      // 2、数组写法,计算属性名就是getters中的属性名
      ...mapGetters(['tenfold'])
    }
}
</script>

mapActions

替代调用this.$store.dispatch(),生成对应actions中的方法,作为组件methos配置

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {
    add(context,val){
        val += 1;
        context.commit("add",val);
    }
}

const mutations = {
    add(state,val){
        state.num += val;
    }
}

const state = {
    num: 0
}

const getters = {}

export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
});
<template>
  <div>
    {{$store.state.num}}
    <!-- 此处的add是mapActions构造的,这个函数默认有一个参数 -->
    <!-- 如果就是add的val参数,如果不传,默认是event对象 -->
    <button @click="add(1)">add</button>
  </div>
</template>

<script>
//引入mapState
import {mapActions} from 'vuex';
export default {
    name: "App",
    methods: {
      //和mapState一样,同样支持对象或数组的写法
      ...mapActions(['add'])
    },
}
</script>

mapMutations

替代调用this.$store.commit(),生成对应mutations中的方法,作为组件methos配置

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);

const actions = {}

const mutations = {
    add(state,val){
        state.num += val;
    }
}

const state = {
    num: 0
}

const getters = {}

export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
});
<template>
  <div>
    {{$store.state.num}}
    <!-- 此处的add是mapMutations构造的,这个函数默认有一个参数 -->
    <!-- 如果就是add的val参数,如果不传,默认是event对象 -->
    <button @click="add(1)">add</button>
  </div>
</template>

<script>
//引入mapState
import {mapMutations} from 'vuex';
export default {
    name: "App",
    methods: {
      //和mapState一样,同样支持对象或数组的写法
      ...mapMutations(['add'])
    },
}
</script>

vuex的模块化开发

在实际开发中,如果大型项目,那么可能在actions、mutations、state、getters中,多种业务的数据、逻辑都写在同一个对象中,增大了耦合,而且不容易维护。所以,vuex提供了模块化的开发方法,可以将每个业务的actions、mutations、state、getters单独的写在一个对象(文件导入)中,互不影响,组件在使用的时候,可以按照需要对namespace使用map...进行引入

例如,有支付、订单两个业务:

payOptions.js用于写支付业务的actions、mutations、state、getters

export default{
    namespaced: true, //开启namespaced,才可以按照名称进行引入
    state: {
        money: 10000
    },
    actions: {
        add(context,val){
            context.commit('add',val);
        }
    },
    mutations: {
        add(state,val){
            state.money += val
        }
    },
    getters: {

    }
}

orderOptions.js用于写订单业务的actions、mutations、state、getters

export default {
    namespaced: true, 
    state: {
        totalOrder: 1314
    },
    actions: {
        sub(context,val){
            context.commit('sub',val);
        }
    },
    mutations: {
        sub(state,val){
            state.totalOrder -= val;
        }
    },
    getters: {

    }
}

index.js,引入每个模块的文件,并配置到store

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex);
// 引入业务模块
import payOptions from './payOptions';
import orderOptions from './orderOptions';

export default new Vuex.Store({
    //配置业务模块到store
    modules: {
        payOptions,
        orderOptions
    }
});

App.vue,分别引入每个模块的state、actions进行测试

<template>
  <div>
    剩余资金:{{money}},剩余订单:{{totalOrder}}<br/>
    <button @click="add(100)">addMoney</button>
    <button @click="sub(10)">subOrder</button>
  </div>
</template>

<script>
//引入mapState
import {mapState,mapActions} from 'vuex';
export default {
    name: "App",
    methods: {
      ...mapActions("payOptions",['add']),
      ...mapActions("orderOptions",['sub'])
    },
    computed: {
      //此时的第一个参数就是声明使用哪个模块
      ...mapState("payOptions",['money']),
      ...mapState("orderOptions",['totalOrder'])
    }
}
</script>