Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件状态。
也就是将 Vue.js 程序中各个页面公用的数据和获取、改变这些数据的方法抽离出来,方便各个页面调用,以及页面之间的数据传输。
下面通过一个例子来讲解Vuex
我们首先创建一个Vue工程。
我们假设有两个富豪榜,这两个富豪榜分属两个界面,通过组件注册,在一个主界面。
页面结构如下:
我们在父页面 App.vue
的 data
属性中加入人员列表,并引入两个子页面,通过 v-bind
指令将 data
传递给子页面。
<template>
<div id="app">
<!-- 将 data 元素传递给子页面 -->
<salary-list-one v-bind:sendedSalaryList="salaryList"></salary-list-one>
<salary-list-two v-bind:sendedSalaryList="salaryList"></salary-list-two>
</div>
</template>
<script>
import salarylist1 from './components/salarylist1';
import salarylist2 from './components/salarylist2';
export default {
name: "app",
data() {
return {
salaryList: [{
name: "马云",
salaryAmount: 1000
},
{
name: "马化腾",
salaryAmount: 900
},
{
name: "李彦宏",
salaryAmount: 800
}
]
};
},
components: {
'salary-list-one': salarylist1,
'salary-list-two': salarylist2
}
};
</script>
<style scoped>
</style>
子页面通过 props
属性接收父页面传递过来的参数,并展示出来。
salarylist1.vue
代码如下
<template>
<div id="salary-list-fisrt">
<h2>财富榜</h2>
<ol>
// 通过 v-for 命令将其展示出来
<li v-for="salary in sendedSalaryList">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
</div>
</template>
<script>
export default {
name: "salary-listfirst",
// 通过 props 属性接收父页面传来的参数
props: {
sendedSalaryList: {
type: Array,
required: true
}
}
}
</script>
<style>
</style>
salarylist2.vue
代码和上面的代码基本相似
运行结果如下:
虽然页面之间可以使用 props
, $emit
等传递参数,但是这种参数的传递会面临以下问题
问题一 :多个视图依赖于同一状态。
问题二 :来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?
在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护
如果之前没有安装 vuex,使用下面的命令安装
npm install vuex --save
在项目根目录下新建 store/store.js
文件,这个文件中保存的就是这个应用中需要共享的内容, 而且整个应用中只有一个。
在 main.js
文件中引入 vuex
import Vue from 'vue'
import App from './App.vue'
import {store} from './store/store'
Vue.config.productionTip = false
new Vue({
store:store,
render: h => h(App),
}).$mount('#app')
state
类似于 Vue 实例中的 data
属性,不同的是 state
是共享的。
现在将刚才 App.vue
中的 data
保存在store.js
中,将 App.vue
中保存的数据移除。
现在,两个财富榜都没有了数据,如图:
现在改为从 store
请求数据
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
storeSalaryList: [{
name: "马云",
salaryAmount: 1000
},
{
name: "马化腾",
salaryAmount: 900
},
{
name: "李彦宏",
salaryAmount: 800
}
]
}
});
在 财富榜2 中引入获取 store
<template>
<!--salarylist2.vue -->
<div id="salary-list-fisrt">
<h2>财富榜2</h2>
<ol>
<li v-for="salary in salaryList ">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
</div>
</template>
<script>
export default {
name: "salary-list-first",
computed: {
salaryList() {
// 通过 computed 属性,获取到 store.js 中的数据,并返回
return this.$store.state.storeSalaryList;
}
}
}
</script>
<style>
</style>
在 App.vue 中去掉 v-bind
指令
<template>
<div id="app">
<salary-list-one ></salary-list-one>
<salary-list-two ></salary-list-two>
</div>
</template>
<script>
import salarylist1 from './components/salarylist1';
import salarylist2 from './components/salarylist2';
export default {
name: "app",
data() {
return {
};
},
components:{
'salary-list-one':salarylist1,
'salary-list-two':salarylist2
}
};
</script>
<style scoped>
</style>
运行结果如下:
如果在 salarylist1.vue 中也加入上面的 store 引用,就可以连个财富榜就可以全部展示。
在一些情况下,当获取到 state
的数据之后,我们希望进行一些加工处理再进行展示。
现在,我们将上面每个人的工资翻倍,再增加 **美元($)**符号。
修改 salarylist1.vue
和 salarylist2.vue
代码,在 computed
属性中增加,doubleSalary
属性
<template>
<div id="salary-list-fisrt">
<h2>财富榜</h2>
<ol>
<li v-for="salary in doubleSalary">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
</div>
</template>
<script>
export default {
computed: {
salaryList() {
return this.$store.state.storeSalaryList;
},
// doubleSalary 属性对每个人 state 的数据加工后进行返回
doubleSalary() {
var afterDoubleSalary =
this.$store.state.storeSalaryList.map(salary => {
return {
name: salary.name,
salaryAmount: salary.salaryAmount * 2 + " " + "$"
};
});
return afterDoubleSalary;
}
},
}
</script>
<style>
</style>
运行结果如下
以上的代码虽然对 state
属性进行了加工之后返回,但是有一定的局限性,如果有100个页面都要这样处理,是不是就要将 doubleSalary
属性写100遍?这样显然是不合理的,为了解决这个问题,可以 doubleSalary
属性写进 store.js
作为一个 getter
方法。
在 store.js
中增加一个getter
方法
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
storeSalaryList: [{
name: "马云",
salaryAmount: 1000
},
{
name: "马化腾",
salaryAmount: 900
},
{
name: "李彦宏",
salaryAmount: 800
}
]
},
getters: {
// 增加一个 getter 方法,用于返回加工后的 工资
doubleSalaryGetter: (state) => {
var afterDoubleSalary =
state.storeSalaryList.map(salary => {
return {
name: salary.name,
salaryAmount: salary.salaryAmount * 2 + " " + "$"
};
});
return afterDoubleSalary;
}
}
});
修改 salarylist1.vue
和 salarylist2.vue
代码,在 computed
属性中增加,doubleSalaryByGetter
属性,在这个属性中,调用 store.js
中的 getter
返回结果。
<template>
<div id="salary-list-fisrt">
<h2>财富榜</h2>
<ol>
<li v-for="salary in doubleSalaryByGetter">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
</div>
</template>
<script>
export default {
computed: {
salaryList() {
return this.$store.state.storeSalaryList;
},
// 在这里调用 store.js 中的方法。
doubleSalaryByGetter() {
return this.$store.getters.doubleSalaryGetter;
}
},
}
</script>
<style>
</style>
如果我们 还有一个需求:计算工资总额,可以在 getter
新增加一个 totalSalary
方法
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
storeSalaryList: [{
name: "马云",
salaryAmount: 1000
},
{
name: "马化腾",
salaryAmount: 900
},
{
name: "李彦宏",
salaryAmount: 800
}
]
},
getters: {
doubleSalaryGetter: (state) => {
var afterDoubleSalary = state.storeSalaryList.map(salary => {
return {
name: salary.name,
salaryAmount: salary.salaryAmount * 2 + " " + "$"
};
});
return afterDoubleSalary;
},
// 在这里增加一个 计算工资总额的方法
totalSalary: (state) => {
var sum = 0
state.storeSalaryList.forEach(element => {
sum += element.salaryAmount;
});
return sum*2;
}
}
});
修改 salarylist1.vue
代码,在 computed
属性中增加,totalSalaryByGetter
属性,在这个属性中,调用 store.js
中的 getter
返回结果。
<template>
<div id="salary-list-fisrt">
<h2>财富榜</h2>
<ol>
<li v-for="salary in doubleSalaryByGetter">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
<span>工资总额是: {{totalSalaryByGetter}}</span>
</div>
</template>
<script>
export default {
computed: {
salaryList() {
return this.$store.state.storeSalaryList;
},
doubleSalaryByGetter() {
return this.$store.getters.doubleSalaryGetter;
},
// 增加获取工资总额的方法
totalSalaryByGetter() {
return this.$store.getters.totalSalary;
}
},
}
</script>
<style>
</style>
我们通过在当前页面调用 store.js
的 getter
方法,可以返回想要的结果。但是发现,在每个页面中,都要写调用写一个方法来调用 getter
也不是很方便。
Vuex 为我们提供了一个 mapGetters
辅助函数,可以用来获取getter
<template>
<div id="salary-list-fisrt">
<h2>财富榜2</h2>
<!--
<ol>
<li v-for="salary in doubleSalaryGetter ">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
<span>工资总额是:{{totalSalary}}</span>
-->
<ol>
<li v-for="salary in myDoubleSalaryGetter ">
{{salary.name}}的工资是:{{salary.salaryAmount}}
</li>
</ol>
<span>工资总额是:{{myTotalSalary}}</span>
</div>
</template>
<script>
import {
mapGetters
} from 'vuex';
export default {
name: "salary-list-two",
computed: {
salaryList() {
return this.$store.state.storeSalaryList;
},
doubleSalaryByGetter() {
return this.$store.getters.doubleSalaryGetter;
},
/**
...mapGetters(
// 注意这里是个数组
[
'doubleSalaryGetter','totalSalary'
]
)*/
// ...mapGetters的另一种写法,通过这种方法可以给 getter 起别名
...mapGetters(
// 注意这里是一个对象
{
myDoubleSalaryGetter: 'doubleSalaryGetter',
myTotalSalary: 'totalSalary'
})
}
}
</script>
<style>
</style>
运行结果如下: