本文基于上一篇文章:Vue系列入门教程(14)-vuex入门案例
VueX中的核心成员列表如下:
state: 存放状态,类似于vue组件中的datamutations:操作state成员,必须是同步方法,类似于vue组件中的methodsgetters: 加工state成员给外界,类似于vue组件中的computedactions: 异步操作modules: 模块化状态管理
state的使用在上篇文章演示过了,存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,可以通过this.$store在所有组件中获取。
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,不适应vuex情况下,我们可能会用计算属性:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
1、将上篇文章中的vuex-app项目中store下的index.js修改如下:
import Vue from 'vue'
// 导入vuex
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建Vuex对象
const store = new Vuex.Store({
state:{
products:[
{name:"小米手机",price:"5000",country:"CN"},
{name:"苹果手机",price:"6000",country:"US"},
{name:"魅族手机",price:"4000",country:"CN"},
{name:"Vivo手机",price:"3000",country:"CN"}
]
},
getters:{
// Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
cnProducts:state => {
return state.products.filter(product => product.country == 'CN');
}
}
})
// 默认导出store
export default store
2、在components目录下,新建Product.vue:
<template>
<div>
<ul>
<li v-for="(item,i) in cnProducts" :key="i">
商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
</li>
</ul>
</div>
</template>
<script>
export default {
computed:{
cnProducts(){
// Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值:
return this.$store.getters.cnProducts;
}
}
}
</script>
<style scoped>
</style>
$store.getters 对象,可以以属性的形式访问这些值,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的3、修改App.vue如下:
<template>
<div id="app">
<Product></Product>
</div>
</template>
<script>
import Product from './components/Product.vue'
export default {
name: 'app',
components: {
Product
}
}
</script>
<style>
</style>
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
1、修改store\index.js中Store对象如下:
//创建Vuex对象
const store = new Vuex.Store({
state:{
products:[
{name:"小米手机",price:"5000",country:"CN"},
{name:"苹果手机",price:"6000",country:"US"},
{name:"魅族手机",price:"4000",country:"CN"},
{name:"Vivo手机",price:"3000",country:"CN"}
]
},
getters:{
// Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
getProductByCountry:state =>(country)=> {
return state.products.filter(product => product.country == country);
}
}
})
2、修改Product.vue的computed如下:
computed:{
cnProducts(){
// Getter 会暴露为 $store.getters 对象,可以以方法的形式访问这些值:
return this.$store.getters.getProductByCountry("CN");
}
}
3、测试结果还是一样
1、mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。我 们之前说过在组件中不能直接修改state,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
2、mutations方法都有默认的形参:([state] [,payload])
state是当前VueX对象中的statepayload是该方法在被调用时传递参数使用的
1)将小米手机名称改为华为手机
修改store\index.js中Store如下:
//创建Vuex对象
const store = new Vuex.Store({
state:{
products:[
{name:"小米手机",price:"5000",country:"CN"},
{name:"苹果手机",price:"6000",country:"US"},
{name:"魅族手机",price:"4000",country:"CN"},
{name:"Vivo手机",price:"3000",country:"CN"}
]
},
getters:{
// Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
getProductByCountry:state =>(country)=> {
return state.products.filter(product => product.country == country);
}
},
mutations:{
// 将小米手机更名为华为手机
change(state){
state.products[0].name="华为手机";
}
}
})
2)将Product.vue修改如下:
<template>
<div>
<ul>
<li v-for="(item,i) in cnProducts" :key="i">
商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
</li>
</ul>
<button @click="handleClick">更改</button>
</div>
</template>
<script>
export default {
methods:{
handleClick(){
// 通过$store.commit调用mutations中方法
this.$store.commit("change");
}
},
computed:{
cnProducts(){
// Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值:
return this.$store.getters.getProductByCountry("CN");
}
}
}
</script>
<style scoped>
</style>
3)测试点击按钮:

4)在实际生产过程中,会遇到需要在提交某个mutations时需要携带一些参数给方法使用,即 mutation 的 载荷(payload)。
a)单个值提交时:
修改store\index.js中mutations如下:
mutations:{
// 将小米手机更名为某手机
change(state,playload){
state.products[0].name=playload;
}
}
修改Product.vue中handleClick如下:
handleClick(){
// 通过$store.commit调用mutations中方法,传递单个参数
this.$store.commit("change","OPPO手机");
}
测试:

b)多个值组装成对象提交:
修改store\index.js中mutations如下:
mutations:{
// 将小米手机更名为某手机
change(state,playload){
// state.products[0] = playload;这样写页面不会跟着变
state.products.splice(0,1,playload);
}
}
修改Product.vue中handleClick如下:
handleClick(){
// 通过$store.commit调用mutations中方法,提交对象
this.$store.commit("change",{name:"OPPO手机",price:7000,country:"CN"});
}
测试:

c)另一种提交方式
修改Product.vue中handleClick如下:
handleClick(){
// 通过$store.commit调用mutations中方法,提交对象
this.$store.commit({
type:"change",
phone:{name:"OPPO手机",price:7000,country:"CN"}
});
}
修改store\index.js中mutations如下:
mutations:{
// 将小米手机更名为某手机
change(state,playload){
// playload接收的commit提交的整个对象,type和phone都是其中属性
state.products.splice(0,1,playload.phone);
}
}
测试效果一样。
5)Mutation 需遵守 Vue 的响应规则
为了配合Vue的响应式数据,我们在Mutations的方法中,应当使用Vue提供的方法来进行操作。如果使用delete或者xx.xx = xx的形式去删或增,则Vue不能对数据进行实时响应。
a)Vue.set 为某个对象设置成员的值,若不存在则新增
例如对state对象中添加一个title成员:
在store\index.js中mutations新增如下方法:
// 给state添加属性
addTitle(state){
Vue.set(state,"title","手机列表");
}
修改Product.vue中template如下:
<template>
<div>
<h1>{{$store.state.title}}</h1>
<ul>
<li v-for="(item,i) in cnProducts" :key="i">
商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
</li>
</ul>
<button @click="handleClick">更改</button>
</div>
</template>
修改Product.vue中handleClick如下:
handleClick(){
// 通过$store.commit调用mutations中方法,添加属性
this.$store.commit("addTitle");
}
测试:

b)Vue.delete 删除成员,
例如删除state对象中的title成员:Vue.delete(state,“title”);在此就不做演示了。
由于直接在mutation方法中进行异步操作,将会引起数据失效。所以提供了Actions来专门进行异步操作,最终提交mutation方法。
Actions中的方法有两个默认参数:
context上下文(相当于箭头函数中的this)对象payload挂载参数
我们在第三大点中案例中第4)小点c部分将小米手机改为OPPO手机,现在们希望点击更改按钮后果两秒再去执行change方法,由于setTimeout是异步操作,所以需要使用actions。
修改store\index.js中mutations和actions方法如下:
mutations:{
change(state,playload){
state.products.splice(0,1,playload.phone);
}
}
actions:{
// 两秒后提交change
asyncChange(context,palyload){
// playload接收的使整个对象
setTimeout(()=>{
context.commit("change",palyload);
},2000);
}
}
在组件中调用,修改Product.vue中handleClick方法如下:
handleClick(){
// 通过$store.dispatch调用actions中方法
this.$store.dispatch({
type:"asyncChange",
phone:{name:"OPPO手机",price:7000,country:"CN"}
});
}
测试发现点击按钮,2秒后成功更改。
1、当应用变得非常复杂时,store 对象就有可能变得相当臃肿。可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutations、actions、getters、甚至是嵌套子模块——从上至下进行同样方式的分割。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
2、组件内调用模块a的状态:
this.$store.state.a
而commit提交或者dispatch某个方法和以前一样,会自动执行所有模块内的对应type的方法:
this.$store.commit('change')
this.$store.dispatch('asyncChange')
3、模块的局部状态
1)对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
2)同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
3)对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

请参考购物车示例
但也有人习惯store中目录结构如下:
store:.
│ actions.js
│ getters.js
│ index.js
│ mutations.js
│ mutations_type.js ##该项为存放mutaions方法常量的文件,按需要可加入
│
└─modules
Astore.js
对应的内容存放在对应的文件中,和以前一样,在index.js中存放并导出store。state中的数据尽量放在index.js中。而modules中的Astore局部模块状态如果多的话也可以进行细分。






