正如Redux一样,当你不知道是否需要Vuex那就是不需要。不要因为想用Vuex而使用它。
用过Vue的人都知道,Vuex是Vue的一个全局状态管理模块,它的作用是多个组件共享状态及数据,当某个组件将全局状态修改时,在绑定了该状态的另一个组件也将响应。实际上可以将Vue理解为一个function,在Vue的作用域中有一个数据代理,在每个Vue的实例中都能对其读和写
我们都知道Vue的数据驱动原理是用Object.defineProperty()进行数据代理,在setter中对数据绑定的view进行异步响应(vue3.0则是使用proxy)
通过查看Vuex源码可知Vuex的核心原理就是在Vue的beforeCreate钩子前混入(mixin)Vuex,并在init中将$store属性注册到Vue中
为了使案例更具体,我这还是简单使用脚手架搭了个项目(可参考另一篇文章),虽然只有两个组件,但是能清晰的理解其用法,我的src目录如下,除了最基础的App.vue和main.js外只有两个组件和一个store
先说明一下两个组件的作用,第一个组件是输入框,在里面输入字符,在二个组件div中显示,就是这么简单
首先我们使用常规方式(EventBus)实现一下,这里只需要在mainjs中创建一个vue实例,然后注册在vue中就可以通过事件emit和on来进行组件通信
main.js
import Vue from 'vue' import App from './App' Vue.prototype.$eventBus = new Vue() new Vue({ el: '#app', components: {App}, template: '<App/>' })
inputComp.vue
<template> <input type="text" @input="inputHandler" /> </template> <script> export default { name: "inputComp", methods: { inputHandler(e) { this.$eventBus.$emit('changeVal',e.target.value)//输入时通过eventBus发送消息 }, }, }; </script> <style scoped> </style>
divComp.vue
<template> <div> {{ val }} </div> </template> <script> export default { name: "divComp", data () { return { val: '' } }, mounted () { this.$eventBus.$on('changeVal', (e) => {//监听输入事件通过eventBus传递信息 this.val = e }) } } </script> <style scoped> </style>
效果如下:
下面我会逐级介绍Vuex在这个案例中如何使用
首先我们先加入state用来存放数据值(类似于组件中的data),在store中添加一个state,并在main中引入store
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const state = { val: '' } export default new Vuex.Store({ state })
然后在上面的inputHandler中将eventBus换成以下代码
inputHandler(e) { this.$store.state.val = e.target.value; }
将div标签中的val换成this.$store.state.val,这就是最简单的Vuex使用,仅仅通过修改state从而达到全局状态的目的,如果你的项目并不复杂,这个简单的全局状态就足够了
下一步,我们加入一个mutations,这里我们可以将mutations当成发布/订阅的调度中心,将函数写在mutations中相当于注册了一个事件,在页面中通过emit来触发
在store中新增mutations
const state = { val: '' } const mutations = { changeVal (state, _val) { state.val = _val } } export default new Vuex.Store({ state, mutations })
在inputHandler中调用一下该函数,其中changeVal是mutations中的函数名,通过emit传参达到和发布\订阅效果
this.$store.commit('changeVal',e.target.value)
了解了mutations,我们来看看getters,我们都知道Vue中有一个computed计算属性,当被代理的值发生变化时,它会被重新计算,Vuex中的派生属性getters会绑定state中的某个或某些值,通过传递参数,达到对该值过滤,修改的作用
这里我们做个简单的计算,输入字符后计算其长度并拼接一下,inputHandler中操作不变,在store中新增getters
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const state = { val: '' } const mutations = { changeVal (state, _val) { state.val = _val } } const getters = { getValueLength (state) { return `长度:${state.val.length}` } } export default new Vuex.Store({ state, mutations, getters })
然后在div标签中的this.$store.state.val后面添加this.$store.getters.getValueLength
<div> {{ this.$store.state.val+this.$store.getters.getValueLength }} </div>
效果如下:
如果到这一步,你仍然感觉难度不大,那么恭喜你,Vuex的使用已经掌握了一大半了
下面,我们来说说actions,在说actions之前,我们先回顾一下mutations,mutations中注册了一些事件,在组件中通过emit对事件进行触发,达到处理异步且解耦的效果,然而官方并不推荐我们直接对store进行操作
官方对actions的说明是:Action 类似于 mutation,不同在于1.Action 提交的是 mutation,而不是直接变更状态。2.Action 可以包含任意异步操作。
也就是说,我们要把组件中的emit操作放到actions中,而在组件中通过某些方式来触发actions中的函数间接调用emit,此时,为了让action更直观,我们添加一个清除输入框字符的方法,当点击清除按钮时清除state.val
在输入框组件中将value绑定到state上
<template> <input type="text" @input="inputHandler" :value="this.$store.state.val" /> </template> <script> export default { name: "inputComp", methods: { inputHandler(e) { this.$store.dispatch("actionVal", e.target.value); }, }, }; </script> <style scoped> </style>
在另一个显示数据的组件中新增删除按钮并绑定删除事件,通过dispatch告知store并通过emit操作state
<template> <div> <button @click="clickHandler">清除</button> <span>{{ this.$store.state.val + this.$store.getters.getValueLength }}</span> </div> </template> <script> export default { name: "divComp", methods: { clickHandler(){ this.$store.dispatch('actionClearVal') } }, }; </script> <style scoped> </style>
最后在store中新建删除的actions和mutations
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const state = { val: '' } const mutations = { changeVal(state, _val) { state.val = _val }, clearVal(state, _val) { state.val = '' } } const actions = { actionVal(state, _val) { state.commit('changeVal', _val) }, actionClearVal(state) { state.commit('clearVal') } } const getters = { getValueLength(state) { return `长度:${state.val.length}` } } export default new Vuex.Store({ state, mutations, actions, getters })
最终效果如下:
到这里为止,Vuex的基本用法就介绍完毕了。
然而除此之外,Vuex官方还提供了辅助函数(mapState,mapMutations,mapGetters,mapActions)和Modules(store的子模块,当有许多全局状态时,我们为了避免代码臃肿,就可以将各个store分割成模块)方便我们书写
下面我们用辅助函数重新实现一下上述功能
输入框:
<template> <input type="text" @input="inputHandler" :value="value" /> </template> <script> import { mapState, mapMutations } from "vuex"; export default { name: "inputComp", computed: { ...mapState({ value: "val" }), }, methods: { ...mapMutations({ sendParams: "changeVal" }), // sendParams用来传递参数,先把sendParams注册到mutations上,输入时触发sendParams inputHandler(e) { this.sendParams(e.target.value); }, }, }; </script> <style scoped> </style>
显示框:
<template> <div> <button @click="clickHandler">清除</button> <span>{{ value + valueLength }}</span> </div> </template> <script> import { mapState, mapGetters, mapActions } from "vuex"; export default { name: "divComp", computed: { ...mapState({ value: "val" }), ...mapGetters({ valueLength: "getValueLength" }), }, methods: { ...mapActions({ clickHandler: "actionClearVal" }), }, }; </script> <style scoped> </style>
这里需要注意的一点就是mapActions和mapMutations的传参问题,这里我使用另一个函数接收参数并且注册到store中
关于Modules的这里就不多做介绍,官方已给出了详细说明