VUE状态管理之Vuex

什么是Vuex

Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。——官方介绍

对于第一次接触Vuex的新手来说,看官方介绍多少还是有些迷糊。通俗一点来说,Vuex就是状态管理工具、数据管理工具,帮我们的应用管理着共享状态,解决了组件之间共享同一状态的麻烦问题的一个插件。


为什么要使用Vuex

可以使用的组件间传值方案

  • 通过路由我们可以携带一些简单的参数;
  • 使用prop和$emit来进行通讯;
  • 使用EventBus(事件总线);
  • 更多的其他方式...

遇到的问题

  • 我们先来描述一种场景,当项目中多个组件都依赖同一个状态的时候,如果是多层嵌套的父子组件,使用prop和$emit来进行传递会显得颇为繁琐;如果是兄弟组件要想进行传递,你或许可以使用EventBus(事件总线)来进行交互,不得不承认,它非常的灵活,但是当事件变多了以后维护起来会变得颇为麻烦,而且这种发布者-订阅者的模式下,发布于订阅必须是成对出现的,在订阅事件的组件里,必须要手动销毁监听,不然会多次执行。

Parent.vue

<template>
    <div>
      <h3>我是父组件,今年我{{ parentAge }}岁了!</h3>
      <Child :age="parentAge" @addParentAge="handlerAge"></Child>
    </div>
</template>
<script>
    import Child from './Child'
    export default {
        name:'Parent',
        components:{
            Child
        },
        data () {
            return {
                parentAge: 50
            }
        },
        methods: {
            handlerAge(age) {
                alert('好的,我知道了我的实际年龄是:', age)
            }
        }
     }
</script>

Child.vue

<template>
    <div>
      <h3>我是子组件,父组件告诉我他今年{{ age }}岁了!</h3>
      实际上,父组件今年的生日已经过了,所以他的年龄应该
      <button @click="addAge">加1</button>
    </div>
</template>
<script>
    export default {
        name:'Child',
        props: { 
            age: { type: Number, default: 0 } 
        },
        methods: {
            addAge() {
              this.$emit('addParentAge', this.age + 1);
            }
        }
    }
</script>
  • 有时候我们通过路由来传递某一种或者多种状态(数据),如果状态过多或某些状态的内容过长的时候,这种方式显然也行不通了。
navTo("/pages/discover/sub/activity-detail", { id: id });
  • 随着项目越来越复杂,组件和数据之间的对应关系也变得愈加混乱。

那么Vuex可以解决我们遇到的这些问题么?接下来我们试一下吧!

怎么使用Vuex

安装

npm install vuex --save // 使用 npm

yarn add vuex // 使用 yarn

当然,你可以可以直接下载 vuex 或者CDN引用的方式来使用

<script src="/path/vue.js"></script>
<script src="/path/vuex.js"></script>

目录和文件

src 目录下按照你的需求构建 store 目录以及 js文件,我们可以在这里组装模块并导出 store

使用介绍

一、State
引入 Vuex 后,我们需要在 state 中定义变量,感觉这个有点类似 Vue 中的 data,通过 state 来存放状态。

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    // 存放状态
    state: {
        age: 51,
        name: 'Parent',
        nickName: '父组件'
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
})

这样的话就不用考虑组件间的传值,直接通过 $store.state.age 来获取年龄数据

<template>
    <div>
      <h3>我是子组件,父组件告诉我他今年{{ $store.state.age }}岁了!</h3>
    </div>
</template>

当然,我们也可以把它定义在计算属性 computed 中

template>
    <div>
      <h3>我是子组件,父组件告诉我他今年{{ age }}岁了!</h3>
    </div>
</template>
<script>
    export default {
        name:'Child',
        computed: {
            age() {
                return this.$store.state.age
            }   
        }
    }
</script>

mapState

上面的代码,从使用上来说基本可以满足我们的需求,但是属性多起来的话,感觉代码写得还是有点繁琐,好在 Vuex 还给我们提供了简便方法 mapState

import { mapState } from 'vuex'
export default {
    name:'Child',
    computed: mapState(['age', 'nickName', 'name'])
}
computed: mapState(['age', 'nickName', 'name'])

不得不说,真香!上面这一句代码的作用,等同于下面这段代码:

computed: {
    age() {
        return this.$store.state.age
    }
    nickName() {
        return this.$store.state.nickName
    }
    name() {
        return this.$store.state.name
    }
}

作为一个新手,可能还会有个疑问,你这样写的话,如果我想再新增一个自定义的计算属性怎么办呢?虽然让我少写了几行代码,但是确影响到我继续添加计算属性了。别慌,经过参考项目中大佬的代码发现是使用了 ES6 的展开运算符“...”

computed: {
    realAge() {
        return this.age + 1
    }
    ...mapState(['age', 'nickName', 'name'])
}

二、Getters
从各种资料以及个人的实际操作过程中,可以这样来理解,getters 相当于 Vue 中的计算属性,通过 getters 可以获取我们想要取得的值。

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    // 存放状态
    state: {
        age: 51,
        name: 'Parent',
        nickName: '父组件'
    },
    getters: {
        desc(state) {
            return state.name + '的年龄是' + state.age
        }
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
})

然后可以在 Vue 这样使用它: <div>{{ desc }}</div>

computed: {
    realAge() {
        return this.age + 1
    }
    ...mapGetters(['desc'])
}

三、Mutations

通过提交 mutation 可以实现更改 Vuex 的 store 中的状态。有没有觉得 Mutations 有点类似 Vue 的 methods 事件?每一个 mutation 都有一个字符串的事件类型(type)和一个回调函数,这个回调函数也就是我们进行状态更改的地方,它可以传入参数,第一个参数时 state,第二个参数时 payload,直接来看看代码吧!

mutations: {
    addAge(state, payload) {
        state.age += payload.years
    }
},

在页面部分添加一个测试方法

<template>
    <div>
      <h3>3年过去了,多少岁了?</h3>
      <div>
        <button @click="tellMe">点击我告诉你</button>
      </div>
    </div>
</template>

payload 参数可以是对象类型,这样可以传递更复杂的参数

methods: {
    tellMe() {
        this.$store.commit('addAge', { years: 3 })
    }
}

如果我们需要操作多个数据的时候,代码也会有点多,和 mapState、mapGetters 类似,mutations 也有映射函数 mapMutations,使用它可以帮助我们简化代码。

mapMutations

直接上代码

import { mapMutations } from 'vuex'
export default {
    methods: {
        ...mapMutations(['addAge'])
    }
}

上述代码相当于下面这段代码

import { mapMutations } from 'vuex'
export default {
    methods: {
        addAge(payload) {
            this.$store.commit('addAge', payload)
        }
    }
}

然后可以发起调用

methods: {
    tellMe() {
        this.addAge({ years: 3 })
    }
}

甚至你也可以这样调用

<button @click="addAge({ years: 3 })">点击我告诉你</button>

注意点:mutations 不推荐写异步方法,比如 axios、setTimeout 等,mutations主要是的作用是修改 state。

使用常量替代事件类型

查看原来项目中的代码,发现可以把方法名称用常量来代替。这样的话可以很大程度上避免写错,eslint也会提示错误位置。

import Vue from 'vue'
import Vuex from 'vuex'
export const ADD_AGE = 'addAge'
Vue.use(Vuex)
export default new Vuex.Store({
    // 存放状态
    state: {
        age: 51,
        name: 'Parent',
        nickName: '父组件'
    },
    getters: {
        desc(state) {
            return state.name + '的年龄是' + state.age
        }
    },
    mutations: {
        [ADD_AGE](state, payload) {
            state.age += payload.years
        }
    },
    actions: {
    },
    modules: {
    }
})

把 addAge 方法名定义为一个常量,调用的时候直接引入进来

import { ADD_AGE } from '../store'
import { mapMutations } from 'vuex'
export default {
    methods: {
        ...mapMutations([ADD_AGE])
    }
}

通过阅读 H5 的项目代码,发现是新建了一个 types.js 的文件,专门用来存储这些常量,然后在需要使用的 store js 文件里引入, 这样看起来确实统一一些更有利于管理,具体代码这里就不贴了,参见项目。

四、Actions

Action 类似于 mutation,区别在于 Actions 提交的是 mutaion,而不是直接修改状态 state,另外 Actions 可以包含异步操作。

先在 actions 中定义一个方法,并且返回一个对象

actions: {
    getUserInfo() {
        return {
            age: 51,
            name: 'Parent',
            nickName: '父组件'
        }
    }
}

然后在 Vue 文件的 created 中调用该方法,并且将结果赋值给 userInfo,并且打印。从控制台来看,是一个 Promise ,这说明 actions 中的方法默认就是异步的。

created() {
    var userInfo = this.getUserInfo()
    console.log(userInfo)
},
methods: {
    ...mapActions(['getUserInfo'])
}

Action 通过 store.dispatch 方法触发

this.$store.dispatch('getUserInfo')

而上述代码中的 ...mapActions(['getUserInfo']) 则相当于以下代码

getUserInfo() {
    return this.$store.dispatch('getUserInfo')
}

在实际项目中,一般 state 的属性值都是空的,在完成登录操作后才可以获取到用户信息。下面简单看一下模拟的代码:

created() {
    this.getUserInfo()
}
methods: {
    ...mapActions(['getUserInfo'])
}
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    // 存放状态
    state: {
        age: 51,
        name: 'Parent',
        nickName: '父组件'
    },
    mutations: {
        setUserInfo(state, payload) {
            state.age = payload.age
            state.name = payload.name           
            state.nickName = payload.nickName
        }
    },
    actions: {
        async getUserInfo(context) {
            const res = await axios.get('/api/fetchUserInfo')
            context.commit('setUserInfo', res)
        }
    },
    modules: {
    }
})

简单分析一下,如果要给 state 赋值,肯定会先想到通过 mutations 来操作 state,但是数据是从网络请求接口异步去取得的,所以还不能用 mutations 而是用 actions,也就是说:通过 actions 来操作 mutations,从而再去操作 state。

在运行的过程中,先是调用 getUserInfo 方法,进入了actions,然后通过 commit 调用了 setUserInfo,并且把 res(用户信息)作为参数传进去,最后将对应的属性逐一赋值给 state。

简单总结:Mutations 负责存入,只要分发给我我就存;Actions 负责中间处理,处理完成我就给到 Mutations,Mutations 你要怎么存我不管,所有对 state 状态赋值都是由 Mutations 来操作的;而 Getters 则只负责取,不进行修改。

五、Modules

想象一下,随着项目的发展,store文件的东西越来越多,变得非常臃肿,这种单一状态树的做法变得不太好维护。 Vuex 为了解决上述问题,允许我们将 store 分割成模块(module),每个模块都可以拥有自己的 state、mutation、action 以及 getter,甚至还能嵌套子模块。

const userModule = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}

const customerModule = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}

const store = new Vuex.Store({
    modules: {
        user: userModule,
        customer: customerModule
    }
})
store.state.user  // userModule的状态
store.state.customer // customerModule的状态
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 一、什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有...
    紫月凌枫阅读 10,080评论 0 16
  • 1、vuex简介以及创建一个简单的仓库 vuex是专门为vue框架而设计出的一个公共数据管理框架,任何组件都可以通...
    不见_长安阅读 305评论 0 0
  • 可用于登录的状态管理 一、vuex包含五个基本的对象: state:存储状态,也就是变量; getters:派生状...
    林夕Yola阅读 606评论 0 0
  • 前言   本文假设读者已经了解vue的基础语法,能够使用vue编写出简单的页面 Vue 组件间的通信 为了更全面认...
    果汁凉茶丶阅读 1,281评论 0 10
  • 一、vuex的简单介绍 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,简单的来说作用是:可以简单...
    曼少女阅读 444评论 0 0