# Vue2教程 - 16 Vuex

Vuex 是 Vue 配套的公共数据管理工具,它可以把一些组件共享的数据,保存到 Vuex 中,方便整个程序中的任何组件直接获取或修改这些公共数据。

之前学了父子组件之间传递数据,通过属性回调函数的形式来传递。但是如果涉及到多级组件嵌套,各个组件之间传递数据会非常麻烦。尤其是遇到没有父子关系的组件,在其间传递数据会更麻烦。

Vuex 就是为了保存组件之间的共享数据,如果组件之间有要共享的数据,可以把数据保存到 Vuex 中,Vuex就是提供一个全局的共享数据存储区域,相当于一个数据仓库,各个组件都可以从中读取数据。

# 16.1 安装Vuex

# 1 安装vuex

# npm方式安装
npm install vuex@3 --save

# yarn方式安装
yarn add vuex@3
1
2
3
4
5

Vue2 中只能使用 vuex3版本。

# 2 创建数据存储对象

如果在一开始新建项目的时候,就勾选了Vuex,那么会在项目的 src 下创建一个 store 目录,并包含了一个 index.js文件。

所以如果你创建项目的时候,没有勾选,那么我们也按照这样的方式来操作。首先在 src 下创建一个 store 目录,然后在其下创建一个 index.js文件,内容如下:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});
1
2
3
4
5
6
7
8
9
10
11
12

在上面的代码中,是时候用Vuex创建了一个数据存储对象,在其中有一些属性选项,这个后面再解释。

其中:

  • state 属性是用来存储的公共数据;
  • mutations 属性是用来操作 state 中的数据的方法,只能通过 mutations 中提供的方法来操作 state 中的数据;

# 3 挂载数据存储对象

在项目的 main.js 中引入 Vuex 的数据存储对象,并挂载到Vue实例上。

import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from "./router";
// 1.引入Vuex
import store from "./store";

Vue.config.productionTip = false

new Vue({
  router,
  store,  // 2.挂载
  render: h => h(App),
}).$mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们已经将 store 挂载到了 Vue 实例上,那么任何组件对象都可以通过 $store.state.属性 的方式获取到属性的值。

修改 store 中的数据千万不要这么用 this.$store.state.属性=值 的方式来修改,不符合 Vuex 的设计理念。因为直接操作 state 中的数据,因为万一导致了数据的紊乱,不能快速定位到错误的原因,因为每个组件都可能有操作数据;所以如果要操作 store 中的 state 值,只能通过调用 mutations 提供的方法,才能操作对应的数据。

下面演示一下 Vuex 的使用。

# 16.2 使用Vuex

下面通过一个样例来学习 Vuex 的运用。

实现一个功能,在一个组件中有两个按钮:增减和减少,当点击增减和减少按钮的时候,修改 store 中的值,在另一个组件中获取到store 中的数据并显示出来。这两个组件是平级的关系,没有父子关系,在之前是很难做到的。

# 1 编写store/index.js

  1. store 对象的 state 属性中添加一个 count 属性,用来存储显示的值,当增减和减少的时候,就操作这个 count属性;
  2. storemutations 定义两个方法,用来增减和减少 count 的值,state 中的数据只能通过 mutations 中的方法来操作;

store/index.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        // 定义数据
        count: 0
    },
    //mutations中的方法的第一个参数就是state对象
    mutations: {
        // 增加count
        incrementCount(state) {
            state.count++
        },
        // 减少count,接收一个减数
        decrementCount(state, subtrahend) {
            state.count -= subtrahend;
        }
    },
    getters: {},
    actions: {},
    modules: {},
});
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
  • mutations 中的方法最多只能有两个参数,第一个参数是固定的,就是 state 对象,第二个参数我们在调用的时候可以传值过来,如果想传递多个值,只能通过对象的形式来传值,在对象中定义多个属性。

  • 怎么调用 mutations 中的方法,后面会讲到;

# 2 定义两个组件

编写 CounterCom.vue 组件,在组件中只是通过 $store.state.count 来获取 storestate 中的 count 数据,还使用了一个 input 绑定了 count 的数据,这样 state 中的数据发生了变化就会显示在 CounterCom.vue 组件上。

代码如下:

<template>
    <div>
        <!-- 通过$store.state.属性获取数据 -->
        <h3>{{ $store.state.count }}</h3>

        <!-- 和store中的值实现绑定 -->
        <input type="text" v-model="$store.state.count">
    </div>
</template>

<script>
</script>
1
2
3
4
5
6
7
8
9
10
11
12

注意:form元素不能和state中的属性实现双向绑定,如果要实现双向绑定,需要使用组件和组件自己data中的数据实现双向绑定,同时监听组件中数据的变化,然后修改state中的数据。上面的不规范。


创建 OperateCom.vue 文件,主要是用来操作 Vuex 中的数据。

编写代码如下:

<template>
    <div>
        <button @click="increment">增加</button>
        <button @click="decrement">减少</button>
    </div>
</template>

<script>
export default {
    methods: {
        increment() {
            // 千万不要使用this.$store.state.count++;,不符合 vuex 的设计理念
            //调用store中的方法
            this.$store.commit("incrementCount");
        },
        decrement() {
            this.$store.commit("decrementCount", 2);
        }
    }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在组件中定义了增减和减少的按钮,当点击按钮的时候,调用组件中定义的方法,在定义的方法中操作 store 中的数据,通过this.$store.commit 来调用 storemutations 中定义的方法。

# 3 使用两个组件

我就直接在 App.Vue 中演示了,在其中引入并使用这两个组件;

<template>
  <div>
    <CounterCom></CounterCom>
    <br>
    <OperateCom></OperateCom>
  </div>
</template>

<script>
import CounterCom from '@/components/CounterCom.vue';
import OperateCom from  '@/components/OperateCom.vue';

export default {
  data: function() {
    return {
    }
  },
  components: {
    CounterCom,
    OperateCom
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

运行项目,当点击OperateCom组件中的增加和减少的按钮的时候,CounterCom中显示的数值会自动变化。

# 16.3 getters属性

当从 Vuex 中获取数据的时候,想要做一些转换,可以使用 getters ,例如在之前的 state 中存储的 count,如果想要获取 count 的时候,获取到的值前面加上 总计: 那么就可以使用 getters

store 中定义 getters 属性,在属性中定义对应的属性及对应的函数。

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        // 定义数据
        count: 0
    },
    //mutations中的方法的第一个参数就是state对象
    mutations: {
        // 增加count
        incrementCount(state) {
            state.count++
        },
        // 减少count,接收一个减数
        decrementCount(state, subtrahend) {
            state.count -= subtrahend;
        }
    },
    getters: {
        optCount: function (state) {
            return '总计:' + state.count;
        }
    },

    actions: {},
    modules: {},
});
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

通过上面定义的 getter,在获取的时候,就可以通过 $store.getters.optCount 来获取转换后的 count 的值了。


CounterCom 组件中,可以获取转换后的 count 值,当然也可以直接获取 count 的值

<template>
  <div>
    <!-- 获取转换后的count的值 -->
    <h3>{{ $store.getters.optCount }}</h3>
    <br>
    <!-- 直接获取count的值 -->
    <input type="text" v-model="$store.state.count">
  </div>
</template>
1
2
3
4
5
6
7
8
9

显示效果:


总结:

  1. state 中的数据,不能直接修改,如果想要修改,必须通过 mutations
  2. 如果组件想要直接 从 state 上获取数据: 需要this.$store.state.属性
  3. 如果组件想要修改数据,必须使用 mutations 提供的方法,需要通过 this.$store.commit('方法的名称',唯一的一个参数);
  4. 如果需要对 storestate 上的数据一层包装,然后对外提供,那么推荐使用 getters, 如果需要使用 getters ,则用 this.$store.getters.函数;

# 16.4 actions

类似于 mutation,不同在于 Action 提交的是 mutation,而不是直接修改 state 中的数据。另外 mutations 只能进行同步操作,但 actions 可以包含异步逻辑。

举个栗子:

# 1 定义actions属性

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        // 定义数据
        count: 0
    },
    //mutations中的方法的第一个参数就是state对象
    mutations: {
        // 增加count
        incrementCount(state) {
            state.count++
        },
        // 减少count,接收一个减数
        decrementCount(state, subtrahend) {
            state.count -= subtrahend;
        }
    },
    getters: {
        optCount: function (state) {
            return '总计:' + state.count;
        }
    },
    actions: {    // ----定义action
        incrementCountAction(context) {
            // 调用的是mutations
            context.commit('incrementCount');
        },
        decrementCountAction(context, value) {
            setTimeout(() => {
                // 调用的是mutations
                context.commit('decrementCount', value);
            }, 1000);
        }
    },
    modules: {},
});
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

# 2 在组件中调用 Actions

在组件中,你可以通过 this.$store.dispatch 来调用 actions

<template>
    <div>
        <button @click="increment">增加</button>
        <button @click="decrement">减少</button>
    </div>
</template>

<script>
export default {
    methods: {
        increment() {
            //调用store中的action
            this.$store.dispatch("incrementCountAction");
        },
        decrement() {
            //调用store中的action
            this.$store.dispatch("decrementCountAction", 2);
        }
    }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

actions 可以处理异步操作,例如从服务器获取数据。也可以在一个 action 中调度多个 mutations,主要用来处理更复杂的逻辑。

# 16.5 modules属性

在 Vuex 中,modules 允许你将 store 分割成多个模块,每个模块拥有自己的 statemutationsactionsgetters,甚至是嵌套子模块。这种分割方式有助于将大型应用的 store 组织得更加合理和清晰。

举个栗子:

假设我们有一个项目,其中包含两个模块:usergoods。下面介绍一下如何在 Vuex 中使用这两个模块。

# 1 定义模块

usergoods 模块创建单独的 js 文件,也在 src/store 文件夹下创建。

user.js

// user.js  
export default {
    namespaced: true, // 需要开启命名空间
    state: {
        userInfo: {
            name: "逗比",
            age: "13"
        }
    },
    mutations: {
        setUserInfo(state, userInfo) {
            state.userInfo = userInfo;
        }
    },
    actions: {
        updateUserInfo({ commit }, userInfo) {
            commit('setUserInfo', userInfo);
        }
    },
    getters: {
        username(state) {
            return 'Hello, ' + state.userInfo.name;
        }
    }
}
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

goods.js

// goods.js  
export default {
    namespaced: true,  // 需要开启命名空间  
    state: {
        goodsList: [
            { id: 1, name: "旺旺大礼包", price: 28.9 },
            // ...其他商品  
        ]
    },
    mutations: {
        // ...商品相关的mutations  
    },
    actions: {
        // ...商品相关的actions  
    },
    getters: {
        // ...商品相关的getters  
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2 注册模块

src/store目录下的 index.js 文件中注册这些模块。

// index.js  
import Vue from 'vue';  
import Vuex from 'vuex';  

// 引入两个模块
import user from './user';  
import goods from './goods';  
  
Vue.use(Vuex);  
  
export default new Vuex.Store({  
  modules: {  
    user,  
    goods  
  }  
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3 在组件中使用模块

在组件中,可以通过:

  • 通过 this.$store.state.模块名.状态名 访问状态数据;
  • 通过 this.$store.commit('模块名/mutation名', payload) 调用mutations 提供的方法;
  • 通过 this.$store.dispatch('模块名/action名', payload) 调用 action 中的方法;
  • 通过 this.$store.getters['模块名/getter名'] 来获取getter。

例如,在组件中访问 user模块的 状态getteractionmutation

<template>
    <div>
        <!-- 获取state 中的数据 -->
        <div>{{ this.$store.state.user.userInfo.name }}</div>

        <!-- 调用getter -->
        <div>{{ this.$store.getters['user/username'] }}</div>

        <button @click="testAction">测试Action</button>
        <button @click="testMutation">测试Mutation</button>
    </div>
</template>

<script>
export default {
    methods: {
        testAction: function () {
            const newUserInfo = { name: '牛比', age: 30 };
            // 提交user模块的setUserInfo mutation  
            this.$store.commit('user/setUserInfo', newUserInfo);
        },
        testMutation: function () {
            const newUserInfo = { name: '二比', age: 25 };
            // 分发user模块的updateUserInfo action  
            this.$store.dispatch('user/updateUserInfo', newUserInfo);
        }
    },
}
</script>
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

# 16.6 简化调用Vuex

在 Vuex 中,mapStatemapGettersmapActionsmapMutations 是四个辅助函数,可以帮助我们在 Vue 组件中更简洁地使用 store 的 state、getters、actions 和 mutations。这些辅助函数允许我们将 Vuex store 中的数据和方法映射到组件的计算属性和方法中。

  • mapState 用于将 store 中的 state 映射到组件的计算属性(computed)中。
  • mapGetters 用于将 store 中的 getters 映射到组件的计算属性中。
  • mapMutations 用于将 store 中的 mutations 映射到组件的 methods 中。
  • mapActions 用于将 store 中的 actions 映射到组件的 methods 中。

下面针对 单模块 和 多模块分别介绍一下。

# 1 单模块

首先介绍一下,Vuex 只有一个模块的情况。

首先 Vuex 配置如下:

// index.js  
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        // 定义数据
        count: 0
    },
    //mutations中的方法的第一个参数就是state对象
    mutations: {
        // 增加count
        incrementCount(state) {
            state.count++
        },
        // 减少count,接收一个减数
        decrementCount(state, subtrahend) {
            state.count -= subtrahend;
        }
    },
    getters: {
        optCount: function (state) {
            return '总计:' + state.count;
        }
    },
    actions: {    // ----定义action
        incrementCountAction(context) {
            // 调用的是mutations
            context.commit('incrementCount');
        },
        decrementCountAction(context, value) {
            setTimeout(() => {
                // 调用的是mutations
                context.commit('decrementCount', value);
            }, 1000);
        }
    },
    modules: {},
});
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
41

下面通过 mapStatemapGettersmapMutationsmapActions 简化 Vuex 的调用。

在组件中使用如下:

<template>
    <div>
        <!-- 直接使用count -->
        <div>{{ count }}</div>

        <!-- 直接使用getter -->
        <div>{{ optCount }}</div>

        <!-- 直接使用mutations -->
        <button @click="incrementCount">加(mutation)</button>
        <button @click="decrementCount(2)">减(mutation)</button>

        <!-- 直接使用actions -->
        <button @click="incrementCountAction">加(action)</button>
        <button @click="decrementCountAction(2)">减(action)</button>

        <button @click="call">调用方法</button>
    </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';  

export default {
    computed: {
        ...mapState(['count']),  // 映射state
        ...mapGetters(['optCount'])  // 映射getters
    },
    methods: {
        ...mapMutations(['incrementCount', 'decrementCount']),  // 映射mutations
        ...mapActions(['incrementCountAction', 'decrementCountAction']),  // 映射actions
        
        call: function() {
            // 使用this直接可以调用
            this.incrementCount();
        }
    },
}
</script>
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

# 2 多模块

如果 Vuex 分为多个模块。

还是沿用上面的 user 和 goods 模块。

src/user.js

// user.js  
export default {
    namespaced: true, // 开启命名空间  
    state: {
        userInfo: {
            name: "逗比",
            age: "13"
        }
    },
    mutations: {
        setUserInfo(state, userInfo) {
            state.userInfo = userInfo;
        }
    },
    actions: {
        updateUserInfo({ commit }, userInfo) {
            commit('setUserInfo', userInfo);
        }
    },
    getters: {
        username(state) {
            return 'Hello, ' + state.userInfo.name;
        }
    }
}
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

src/goods.js

// goods.js  
export default {
    namespaced: true, // 开启命名空间  
    state: {
        goodsList: [
            { id: 1, name: "旺旺大礼包", price: 28.9 },
            // ...其他商品  
        ]
    },
    mutations: {
        // ...商品相关的mutations  
    },
    actions: {
        // ...商品相关的actions  
    },
    getters: {
        // ...商品相关的getters  
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

待会只演示调用 user 模块就可以了,调用 goods 模块也是一样的。

src/index.js

// index.js  
import Vue from 'vue';  
import Vuex from 'vuex';  

// 引入两个模块
import user from './user';  
import goods from './goods';  
  
Vue.use(Vuex);  
  
export default new Vuex.Store({  
  modules: {  
    user,  
    goods  
  }  
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在组件中使用如下:

映射的时,需要指定模块的名称。

<template>
    <div>
        <!-- 直接使用 -->
        <div>{{ userInfo.name }} - {{ userInfo.age }}</div>

        <!-- 直接使用getter -->
        <div>{{ username }} - {{ userAge }}</div>
        <div>{{ goodsCount }} - {{ getCount }}</div>

        <!-- 直接使用mutations -->
        <button @click="setUserInfo({name: 'niubi', age: 14})">user(mutation)</button>
        <button @click="setGoodsCount(20)">goods(mutation)</button>

        <!-- 直接使用actions -->
        <button @click="updateUserInfo({name: 'niubi', age: 13})">user(action)</button>
        <button @click="updateGoodsCount(2)">goods(action)</button>

        <button @click="call">调用方法</button>
    </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';

export default {
    computed: {
        ...mapState('user', ['userInfo']),  // 映射user模块的state中的数据
        ...mapState('goods', ['goodsCount']),  // 映射goods模块的state中的数据

        ...mapGetters('user', ['username', 'userAge']),  // 映射getters
        ...mapGetters('goods', ['getCount']),
    },
    methods: {
        ...mapMutations('user', ['setUserInfo']),  // 映射user模块的mutations
        ...mapMutations('goods', ['setGoodsCount']),

        ...mapActions('user', ['updateUserInfo']),  // 映射user模块的actions
        ...mapActions('goods', ['updateGoodsCount']),

        call() {
            this.setUserInfo({name: 'erbi', age: 3});  // 通过this调用
        }
    }
}
</script>
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
41
42
43
44
45

如果多个模块中的 stategettersmutationsactions 存在重名呢?

//----state重名
...mapState({
  // 使用不同的名称来避免冲突
  userStatus: state => state.user.status,
  goodsStatus: state => state.goods.status,
}),
  
//----getters重名
...mapGetters('user', {
  userStatusGetter: 'status'  // 对应 user 模块的 status
}),
...mapGetters('goods', {
  goodsStatusGetter: 'status'
})

//----mutations重名
...mapMutations('user', {
  incrementAge: 'increment' // 对应 user 模块的 increment
}),
...mapMutations('goods', {
  incrementCount: 'increment' // 对应 counter 模块的 increment
}),

//----actions重名
...mapActions('user', {  
  incrementCounterAsync: 'incrementAsync'  // 对应 user 模块的 incrementAsync
}),
...mapActions('goods', {
  incrementAgeAsync: 'incrementAgeAsync'
})
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