fc1b5483b8bce1f69f3ba878c310a403.png

1. 什么是Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。这是官网的说法,其实很简单:就是一个加强版的data! 在单页应用中会有一个data函数,里面就存放了当前页面的一些数据。比如:

<template>
    <div>
        <p>{{num}}</p>
    </div>
</template>
<script>
export default {
    data(){
        return {
            num:'99'
        }
    }
}
</script>

代码中的data就相当于我们这里描述的Vuex。 如果我们的页面比较简单,切记千万不要没事找事引入Vuex,我们使用Vuex是因为项目变得复杂之后,有很多数据需要在父组件、子组件和孙组件之间传递,处理起来很繁琐,于是就需要Vuex这样一个可以对这一部分数据进行统一管理的东西。

2. 全局data就行了,非要这么复杂?

对于我刚刚提到的需求:处理大量的需要在组件间传递的数据,直接定义一个全局的data属性保存就行了,比如这样:this.$root.$data。为什么还要搞一个这么复杂的状态管理? 如果我们按照刚刚所说的搞一个全局变量存放数据其实也行,但是这样有一个问题,就是数据改变后,不会留下变更过的记录,这样不利于调试。 所以我们稍微搞得复杂一点。我们约定组件不能直接修改属于store实例的state,组件必须通过Mutation来改变state,也就是说,组件里面应该执行分发(dispatch)事件通知store去改变。这样约定的好处是,我们能够记录所有store中发生的state改变,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具。

3. 先来一个简单的State

知道了个大概,那我们就实际操作一遍,感觉一下这个听起来很牛逼的东西用起来到底怎么样。 我们使用vue-cli脚手架生成一个Vue项目,并且安装Vuex,最终的项目结构是这样的:

984a54f3987deca7e8195575f2433a41.png

我们在components里面新建一个组件,然后加入以下代码:

<p>{{ count }}</p>
<div>
    <p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
    </p>
</div>
methods:{
    increment(){
    },
    decrement(){
    },
},

接着新建一个vuex文件夹,里面新建一个store.js文件,并加入以下代码:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 应用初始状态
const state = {
    count: 0,
}

// 创建 store 实例
export default new Vuex.Store({
    state,
})

然后我们在组件中就可以使用我们的count变量了:

computed:{
    count(){
        return this.$store.state.count;
    },
}

我们定义一个计算属性,返回store中的count变量:

a5695418097f16a234874ff5d390ecbe.png

3. 计算属性?引入Getter

这里我们可以假想一个场景,我们的页面中现在显示的数字是0,如果我们的需求是这样的,页面中数字如果小于10,就显示为00。那我们可以在刚刚的组件中将计算属性返回的值修改成这样:

return this.$store.state.count > 9 ? this.$store.state.count : '0'+this.$store.state.count;

就可以了,但是如果我们有很多组件都使用了这个count的话,那我们在每一个使用这个变量的地方都需要写一遍这个判断,那为什么不在取数据的时候就把数据整理成想要的样子呢?就和我们组件中的计算属性一样!为了达到目的,我们修改一下store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import * as getters from './getters' //新增

Vue.use(Vuex)

// 应用初始状态
const state = {
    count: 0,
}

// 创建 store 实例
export default new Vuex.Store({
    state,
    getters //新增
})

然后我们新建一个getters.js文件:

export const countaddzero = state => {
    return state.count > 9 ? state.count : '0' + state.count;
}

最后我们改一改组件中的计算属性:

return this.$store.getters.countaddzero;

保存之后发现页面上的数字果然变成了00。

4. 试试修改State,引入Mutation

我们的本意是要做一个点击按钮数字可以增加的一个小demo,现在把数据存到store是完成了。接下来说一下如何修改数据。 为了方便查看,我们先修改一下store.js这个文件:

import Vue from 'vue'
import Vuex from 'vuex'
import * as getters from './getters'

Vue.use(Vuex)

// 应用初始状态
const state = {
    count: 0,
}

// 定义所需的 mutations               //新增
const mutations = {                 //新增
    increment(state, val = 1) {     //新增
        state.count += val;         //新增
    },                              //新增
    decrement(state, val = 1) {     //新增
        state.count -= val;         //新增
    }                               //新增
}                                   //新增

// 创建 store 实例
export default new Vuex.Store({
    state,
    getters,
    mutations                       //新增
})

其中的val表示点击按钮每次需要增加(减少)多少。 有了mutations我们就可以在组件中对store里面的state进行修改了,我们先处理一下组件中的点击事件,当点击加或者减按钮的时候才去修改store里面的值。我们修改一下组件的methods:

methods:{
    increment(){
        this.$store.commit('increment',2); //新增
    },
    decrement(){
        this.$store.commit('decrement',2); //新增
    },
},

其中的2就是之前提到的每一次增加多少,也就是前文的val。我们约定只能通过commit的方式修改store的变量,为什么说是预定呢?手贱的我决定试试:

methods:{
    increment(){
        // this.$store.commit('increment',2); //修改
        this.$store.state.count=1000;         //新增
    },
},

其实这样也可以修改store里面的值,但是这样就不能达到我们刚开始所说的目的了。 一条重要的原则就是要记住mutation必须是同步函数。因为我们不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

5. 异步修改?引入Action

那如果我们就想异步的修改store的值呢?也是有办法的,这时候就需要我们的Action出场了: Action 类似于 mutation,不同在于:

Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。

我们还是先改改代码,我们新建一个actions.js:

export const incrementAsync = ({ commit }, val = 1) => {
    setTimeout(() => {
        commit('increment', val)
    }, 1000)
}
export const decrementAsync = ({ commit }, val = 1) => {
    setTimeout(() => {
        commit('decrement', val)
    }, 1000)
}

这里我们使用setTimeout模拟一下异步执行。接着修改我们的组件

<p> 
    异步:
    <button @click="increment2">+</button>
    <button @click="decrement2">-</button>
</p>

修改一下methods:

methods:{
    increment2(){
        this.$store.dispatch('incrementAsync',2);
    },
    decrement2(){
        this.$store.dispatch('decrementAsync',2);
    },
},

Action通过store.dispatch方法触发,乍一眼看上去感觉多此一举,我们直接分发mutation岂不更方便?实际上并非如此,还记得mutation必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作。 最后修改一下store.js文件: 在顶部导入actions:import * as actions from './action' 然后在实例化的时候加入actions:

// 创建 store 实例
export default new Vuex.Store({
    actions,  //新增
    getters,
    state,
    mutations
})

这样就可以实现异步修改store啦!

6. 不方便维护?引入Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

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 的状态

7. 一点点注意事项

当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会比较棘手:

<input v-model="obj.message">

假设这里的 obj 是在计算属性中返回的一个属于Vuex store的对象,在用户输入时,v-model会试图直接修改obj.message。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。 也就是说其实双向数据绑定和vuex是会有一点冲突的,不过化解的方法也有: 第一种方法: 给 中绑定 value,然后侦听input或者change事件,在事件回调中调用 action:

<input :value="message" @input="updateMessage">

也就是不实用v-model。 第二种方法: 双向绑定的计算属性

<input v-model="message">
computed: {
    message: {
        get () {
            return this.$store.state.obj.message
        },
        set (value) {
            this.$store.commit('updateMessage', value)
        }
    }
}
Logo

前往低代码交流专区

更多推荐