搭建Vuex中央状态管理

      Vuex 通过创建一个集中的数据存储,供程序中所有组件访问。主要应用于 Vue.js 中管理数据状态的一个库。接下来我用一张图来帮助我们更好的理解什么是 Vuex。图中 store 是一个仓库,装的是数据源。下面的 c1-c4,分别代表 component1-component4. 这里面的组件都可以去使用我们 store 里面的数据。Vuex 帮助我们实现了数据统一管理的功能 。


Vuex图示

Vue.js 和 Vuex.js 使用场景中的数据处理

Vue.js

      我们来看下单一使用 Vue 场景下,我们一般是怎么处理数据的。图中最上面有一个根组件,假设里面的 data 有很多数据。在根组件之下,又有两个组件,分别是组件 1 和组件 2,再往下,这两个组件又有自己的子组件。我们知道根组件下面的 2 个组件如果想要获取到根组件的数据,需要通过 vue 当中的prop进行传值。组件下面的 2 个子组件,我们也可以通过prop进行属性传值。但是如果子组件想要去修改一些数据的话,那么子组件就需要触发某一个事件然后传给父组件,然后父组件也需要触发某个事件,再传给他的根组件,这样我们的数据就发送变化了。当我们的数据发生变化后,data 就会分别传给下面的组件 1 和组件 2。这样就会有一个问题,比如最下面的两个子组件想要数据共用的话,就很麻烦。因为要从子组件 2 中一直往上传,基本上是绕了一大圈,然后才到子组件 1。这样是可以做到数据传递的,但是它的使用方式非常的麻烦,


vue.js使用场景

Vuex.js

      接下来我们看看 Vuex 场景中的数据处理是什么样的。我们有一个store用来存储数据,如果我们要添加、更改一些数据。我们就可以用mutation方法去更改一下。子组件中修改了数据,那么store就会把修改后的数据分散到对应的组件,这样就就能更清晰的管理数据了。


vuexjs使用场景.png

安装及使用

      如果在脚手架中没有安装Vuex的,可以使用以下命令安装并使用。脚手架安装过Vuex的,可以忽略这一步。

1.安装

1
npm install vuex —save

2.创建 store.js 并引入对应组件

1
2
3
4
5
6
7
8
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({

})

3.在 main.js 中引入

1
2
3
4
5
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')

核心概念

      Vuex 的核心概念有五个,分别是 State(状态)、Getter(获取状态)、Mutation(改变状态)、Action(通过异步操作 Mutation 改变状态) 以及 Module(将前面几个模块化)。

State

      接下来我将用vue父组件向子组件传值的例子来看看换成vuex的 State 应该怎么去实现。

vue 父组件向子组件传值

父组件

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
<template>
<div id="app">
<product-view-one v-bind:products="products"></product-view-one>
</div>
</template>
<script>
import ProductViewOne from "./components/ProductViewOne";
import ProductViewTwo from "./components/ProductViewTwo";

export default {
name: "app",
data() {
return {
//父组件data
products: [
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
};
},
components: {
"product-view-one": ProductViewOne
}
};
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in products" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "ProductViewOne",
//通过props传值
props: {
products: {
type: Array,
required: true
}
}
};
</script>

State 存储数据

1.将数据统一放在 store.js 中的 state 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
//状态
state:{
products: [
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
}
});

2.对应组件中获取数据

store 实例中读取状态的方法就是在计算属性中返回某个状态,当 state 中对应的数据变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in products" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "ProductViewOne",
computed: {
products() {
//组件能通过 this.$store.state去获取对应的数据
return this.$store.state.products;
},
},
};
</script>

      对比可以看出,Vuex 这种机制去处理庞大的数据是非常舒适的,也非常便捷,不需要使用属性去传值,也不要 event 事件给父级传值,减少代码的冗余度,对于庞大数据,是非常适合的。

mapState 辅助函数

      当一个组件需要获取多个转态时,使用以下的方式会让代码重复且冗余,为了解决这个问题,vuex为我们提供了mapState辅助函数帮我们生成计算属性,减少代码量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
computed: {
products() {
return this.$store.state.products;
},
products2() {
return this.$store.state.products2;
},
当有多个状态时
...products3(),
...products4(),
...
},
}

…mapState([…]);

对应组件

1
2
3
4
5
6
7
8
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["products","products2","..."])
},
}
</script>

Getter 获取数据

      有时我们不单单需要从 store 中的 state 里直接拿取数据,而是需要从 state 中派生出一些转态,比如要拿 state 中某个数字字段的 2 倍。这时候我们就需要用到getter,我们可以把它当做是 store 的计算属性。getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default new Vuex.Store({
//状态
state: {
products: [
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
},
getters: {
saleProducts: (state) => {
//接受 state 作为其第一个参数,简化this.$store.state的写法
let saleProducts = state.products.map(product => {
return {
name: "**" + product.name + "**",
price: product.price * 2
};
});
return saleProducts;
}
},
})

对应组件

1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in saleProducts" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
</div>
</template>

mapGetters 辅助函数

      和mapState类似,mapGetters将 store 中的 getter 映射到局部计算属性,使用对象展开运算符的方式让我们的代码看上去更简洁。

…mapState([…]);

对应组件

1
2
3
4
5
6
7
8
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(["saleProducts"])
},
}
</script>

Mutation

      当我们触发事件后改变数据,就使用mutations,而不再使用computed,也不再使用getters。我们通过点击按钮触发事件的方式来看看mutation怎么用。说到这里,建议大家在谷歌浏览器下安装vue.js devtools的插件,便于我们进行调式。

vue.js devtools

      安装完后开启 vue 的项目,会发现在检查元素中会多一个 vue 节点,点击后可以看到当前项目的组件信息,还可以看到 Vuex 中对应的信息。


vue.js devtools

vue.js 实现

      接下来,我们先来看看触发事件后改变数据在不用Mutation的情况下,是怎么实现的。

对应组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in saleProducts" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
<button @click="reducePrice">触发事件改变price</button>
</div>
</template>
<script>
export default {
methods: {
reducePrice() {
<!-- 通过forEach()遍历数据 -->
this.$store.state.products.forEach(product => {
product.price -= 1;
});
},
}
};
</script>

      虽然在不使用Mutation的时候可以实现,但不是最优解,使用 mutations 可以通过vue.js devtools帮助我们追踪。而且这样写在严格模式下会报以下的错误。

1
[vuex] do not mutate vuex store state outside mutation handlers.

Mutation 实现

      通过Mutation实现,不仅便于我们就可以跟踪事件了,而且严格模式下也不会报错

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default new Vuex.Store({
//状态
state: {
products: [
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
},
//接受 state 作为第一个参数,payload为触发事件中的实参
mutations: {
reducePrice: (state, payload) => {
state.products.forEach(product => {
product.price -= payload;
});
}
},
})

对应组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in saleProducts" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
button @click="reducePrice(6)">传参事件触发更改数据</button>
</div>
</template>
<script>
import { mapState,mapGetters,mapActions } from "vuex";
export default {
methods: {
reducePrice(amout) {
//通过调用 store.commit 方法激活事件,commit中事件的名字取决于mutations
this.$store.commit("reducePrice",amout);
}
};
</script>

注意:Mutation 必须是同步函数

mapMutations 辅助函数

      

…mapMutations([…]);

对应组件

1
2
3
4
5
6
7
8
<script>
import { mapMutations } from "vuex";
export default {
methods: {
...mapMutations(["reducePriceTime"])
},
}
</script>

Action

      Action类似于Mutation,其中最重要的区别就是Mutation必须是同步函数,而Action可以包含任意异步操作。Action提交的是Mutation,而不是直接改变转态。Action接受一个与 store 实例具有相同方法和属性的 context 对象,因此我们可以调用 context.commit 提交一个 Mutation

store.js

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
export default new Vuex.Store({
//状态
state: {
products: [
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
},
mutations: {
reducePrice: (state, payload) => {
state.products.forEach(product => {
product.price -= payload;
});
}
},
actions:{
//这里的context可以理解为this.$state
reducePriceTime: (context, payload) => {
//模拟异步操作
setTimeout(function () {
//激活上面的reducePrice
context.commit('reducePrice', payload);
}, 2000);
}
},
})

我们也可以直接用 ES6 解构的方式代替

1
2
3
4
5
6
7
actions:{
reducePriceTime: ({commit}, payload) => {
setTimeout(function () {
commit('reducePrice');
}, 2000);
}
},

对应组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div id="ProductViewOne">
<p>ProductViewOne</p>
<ul>
<li v-for="(product,index) in saleProducts" :key="index">
<span class="name">{{product.name}}</span>
<span class="price">{{product.price}}</span>
</li>
</ul>
<button @click="reducePriceTime(6)">模拟异步</button>
</div>
</template>

<script>
export default {
methods: {
reducePriceTime(amout) {
//通过 store.dispatch 方法触发
this.$store.dispatch("reducePriceTime", amout);
}
}
};

mapActions 辅助函数

      和mapStatemapGetters的功能和用法都类似,这边不再仔细记录了,直接上代码。

…mapActions([…]);

对应组件

1
2
3
4
5
6
7
8
<script>
import { mapActions } from "vuex";
export default {
methods: {
...mapActions(["reducePriceTime"])
},
}
</script>

Module

      当我们的状态比较多,然后对应的 getter、mutation 又很多时, 那么当前的 store 对象会变得相当臃肿,并且会让当前的代码变得冗余。所以为了解决这个问题,就出现了 module 这么个东西。 接下来我们用 module 把当前的 vuex 模块化并进行整理。

1.在 src 下新增 store 的文件夹
2.在 store 下再新建个文件夹叫 modules,专门用来管理模块化的东西
3.在 modules 中新建对应模块文件,比如 product.js
4.在 store 文件夹下创建 index.js,并删除 src 下的 store.js。main.js 中的 store 在找不到 store.js 的情况下就会去找 store 下的 index.js,目前我们项目 store 的结构如下图


Store目录结构

5.store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import Vuex from 'vuex'
import product from './modules/product'

Vue.use(Vuex);

export default new Vuex.Store({
modules: {
//对应模块
product,
}
});

6.据 state 进行分割,写入对应模块.js

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
const state = {
products:[
{ name: "Lucy", price: 2000 },
{ name: "Daming", price: 1000 },
{ name: "Henry", price: 100 },
{ name: "Jane", price: 10 }
]
}
const getters = {
saleProducts: (state) => {
let saleProducts = state.products.map(product => {
return {
name: "**" + product.name + "**",
price: product.price / 2
};
});
return saleProducts;
}
}

const mutations = {
reducePrice: (state, payload) => {
state.products.forEach(product => {
product.price -= payload;
});
}
}
const actions = {
reducePriceTime: (context, payload) => {
setTimeout(function () {
//激活上面的reducePrice
context.commit('reducePrice', payload);
}, 2000);
}
}

export default {
state, getters, mutations, actions
}

      使用 vuex 写了一个待办事项,具体 demo 可以上github查看

Copyright ©2019 guowj All Rights Reserved.

访客数 : | 访问量 :