快速上手Vuex

103 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

快速上手Vuex

0. 前言

文章是使用Vuex在Vue项目中进行集中的状态管理,以及Vuex常用API的使用,能够在自己项目中实现组件与组件之间的通信,不论在那个组件中都能够随心所欲的获取自己想要的数据。

版本说明:

vue:2.x版本 vux:3.x版本

1. 介绍

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

2. 为什么使用Vuex?

在没有其他状态管理工具的情况下,多个组件之间可能会产生一下几种问题

  • 多个组件之间使用相同的数据,
  • 组件嵌套层级较深,但同时需要与其他组件进行通讯,数据的传递
  • 多个跟模块单独的接入数据

其中最令人感到烦恼的应该是组件间的通讯,因为嵌套的层级太深,或者是兄弟组件之间想要进行数据的传递是非常痛苦的一件事情。那么这时候就需要一个工具来进行统一的数据进行管理,抽象点的表达就是将公共部分的数据放在一个单独的区域,这个公共的区域暂时成为容器。在需要的时候去哪个地方直接取便可以解决这个问题。

放数据的问题解决了,那么操作和获取怎么进行的呢?

这时候就需要Vuex的出场了。下面开始说明Vuex的使用。

3. 引入Vuex

要想使用Vuex就先要进行下载,Vuex的安装方式有很多中,具体的可以参考Vuex文档。这里我是用npm 的方式进行下载使用

npm i vuex@3.0.0 --save

先说一下使用Vuex的项目结构

-- 项目根目录
    -- dist // 项目打包编译后的文件目录
    -- node_modules //npm包的下载目录
    -- public //存放静态资源的文件目录
    -- src 
        ... // 自己的文件目录
        -- store // 容器目录
            -- index.js // Vuex的主文件
        ...
        -- App.vue
        -- main.js

ok,文件目录介绍完毕,其中stroe目录是这次使用Vuex的主要目录.

接下来开始使用Vuex在我们的项目之中

// [/src/stroe/index.js]
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
const store=new Vuex.Store({})
export default store

因为Vuex是依赖与Vue的,所以要先引入Vue,然后在引入Vuex,并使用Vue.use(Vuex)进行全局的注册

随后在项目的main.js文件中进行一些配置

import store from "./store"
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

将store文件夹中的index文件引入进来并且将其配置到Vue实例之中。OK,到这里Vuex的基本配置已经设置完成,但是此时的Vuex没有任何的作用,因为Vuex中核心配置项还并没有进行设置,接下来一块一步步的将Vuex给完善起来。

4. 完善Vuex中配置项

4.1 Vuex的核心概念

  • state(状态)

    存储这stroe(容器的状态)

  • getters

    得到存储在容器中的某一个状态

  • mutations

    设置容器中某个状态的值

  • actions

    操作整个store

  • modules

    模块化容器

看了上面的解释肯定是一脸的懵逼状态...... 不要着急,接下来我会一个一个的讲解每个配置项的作用

4.2 state(状态)

Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT (opens new window))”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 单一状态树

上面的解释具体是什么意思呢? en...官方的语言就是很耐人寻味。让我用大白话来告诉大家。所谓的单一状态数的意思就是整个应用只能拥有一个容器(store)实例。也就是只能有一个状态对象

说了这么多还不知道state是个什么东西,那就代码上见。

// [/src/stroe/index.js]
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
const store=new Vuex.Store({
    state:{
        name:"小明"
    }
})
export default store

没错,他就是一个普通的对象,那么我在这里声明完之后怎么在其他的组件中使用呢?其实很简单。由于在前面的配置中将整个容器(store)都配置到了Vue实例上,所以我们的这个stroe就在Vue实例上,而每个组件中的this都是这个Vue实例,所以通过this就可以进行访问。

新建一个组件A

// A.vue
<template>
  <div>
    <h1>
        <!--通过$store可以得到store对象-->
        {{$store.state.name}}
    </h1>
  </div>
</template>

由于Vuex的状态存储是响应式的,所以当容器中的name字段改变时就会引起页面的重新渲染,这个特性就和计算属性很相似,所以当页面中需要使用容器中的某个字段时放在计算属性中最合适。

// A.vue
<template>
  <div>
    <h1>
        <!--通过$store可以得到store对象-->
        {{name}} 
    </h1>
  </div>
</template>
<script>
    export default {
        computed: {
            name () {
              return this.$store.state.name
            }
    }
</script>

4.3 getters

假如出现一个这样的场景,需要在得到容器中某个状态的同时进行一些其他的操作,并且是很多的地方进行引用,你会怎么进行?

使用this.$store.state.xxx在对该值进行其他处理,可不可以?

答案:可以,但是代码太过冗余并且后期不好维护。

哪有什么好的解决办法?

将公共的逻辑进行抽离,将数据进行处理完成之后再返回出去,而接收方并不需要进行处理。

所以在Vuex中允许使用getters进行上面的操作,怎么理解呢?就当是每个组件的计算属性,它的特性与计算属性一样,当内部所依赖的数据发生改变时会进行重新计算并返回最新的计算值。

同样的代码上见。

// [/src/stroe/index.js]
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
const store=new Vuex.Store({
    state:{
        name:"小明"
    },
    getters: {
    name: state => { 
        // 这里默认会接收一个参数,这个参数的内容就是这个容器state,所以很容易就可以得到状态
      return state.name
    }
  }
})
export default store

竟然有专门的获取状态的方法,那么就要让他发挥它的力量,因此接收的方式发生些许的改变

​
// A.vue
<template>
  <div>
    <h1>
        <!--通过$store可以得到store对象-->
        {{name}} 
    </h1>
  </div>
</template>
<script>
    export default {
        computed: {
            name () {
              return this.$store.getters.name
            }
    }
</script>

这个api只做这一件事情,获取数据

4.4 mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

其实这个函数只做一件事情,就是进行更改状态

// [/src/stroe/index.js]
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
const store=new Vuex.Store({
    state:{
        name:"小明"
    },
    getters: {
    name: state => { 
        // 这里默认会接收一个参数,这个参数的内容就是这个容器state,所以很容易就可以得到状态
      return state.name
    }
  },
    mutations: {
    name (state,value) {
        // 每个回调中的第一个值还是这个容器state,并且只接受一个外部传递的值
      // 变更状态
      state.name = name
    }
  }
})
export default store

在组件中的使用

​
// A.vue
<template>
  <div>
    <h1>
        <!--通过$store可以得到store对象-->
        {{name}} 
        <button @click="changeName">
            更改姓名为张三
        </button>
    </h1>
  </div>
</template>
<script>
    export default {
        computed: {
            name () {
              return this.$store.getters.name
            },
            methods:{
                changeName(){
                    this.$store.commit("name","张三")
                }
            }
    }
</script>

在触发按钮的点击事件时会向容器提交一个更改的指令,执行mutations中的name函数,将状态的中name值进行更改,然后会触发getters中的name执行,引起组件的computed中的name重新计算,达到重新渲染DOM的目的。

注意:

在mutations中不能进行异步操作,比如发送ajax请求

4.5 actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。

  • Action 可以包含任意异步操作。

    // [/src/stroe/index.js]
    import Vue from "vue"
    import Vuex from "vuex"
    Vue.use(Vuex)
    const store=new Vuex.Store({
        state:{
            name:"小明"
        },
        getters: {
        name: state => { 
            // 这里默认会接收一个参数,这个参数的内容就是这个容器state,所以很容易就可以得到状态
          return state.name
        }
      },
        mutations: {
        name (state,value) {
            // 每个回调中的第一个值还是这个容器state,并且只接受一个外部传递的值
          // 变更状态
          state.name = name
        },
        actions: {
            changeName (context,value) {
                // 这里的context是当前容器
              context.commit('name',value)
            }
          }
      }
    })
    export default store
    

    组件使用

​
// A.vue
<template>
  <div>
    <h1>
        <!--通过$store可以得到store对象-->
        {{name}} 
        <button @click="changeName">
            更改姓名为张三
        </button>
    </h1>
  </div>
</template>
<script>
    export default {
        computed: {
            name () {
              return this.$store.getters.name
            },
            methods:{
                changeName(){
                    this.$store.dispatch("changeName","张三")
                }
            }
    }
</script>

这里需要进行解释一下流程:

当点击按钮式触发事件的执行,然后函数执行的操作的是容器actions中的函数,actions中的函数向容器提交了一个name事件触发了mutations中的name的执行,改变 了状态,触发了getters,然后重新渲染Dom。

总结起来就是:

state--(getters)-->组件--(dispatch)-->actions--(commit)-->mutations--(mutate 修改)->state--(getters)-->组件 Vuex是单项数据流,Vue是双向数据绑定. 。 用到v-model,就不要使用vuex; 使用vuex,就不要使用v-model.

4.6 modules

为什么会出现modules?

当所有的状态及相应的操作都放在一个之中,会导致整个store对象变得非常臃肿,为了解决这个问题就产生了将stroe分割成模块,而每个模块都有自己的state、getters、mutations、actiocns甚至再嵌套子模块。

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
​
const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}
​
const store = new Vuex.Store({
    state:{},
    mutations:{},
    actions:{},
    getters:{}
  modules: {
    a: moduleA,
    b: moduleB
  }
})

每个模块可以拥有自己的命名空间,通过设置namespace字段就可以了,此时在调用方法时需要加上前缀。

5 辅助函数

5.1 mapGetters 将仓库的数据成批导入到组件的computed中

//数组:不能改名
computed: {
  ...mapGetters([
    "name",
    "arr",
    "info",
​
  ]),
    a(){
    return 10
  }
},
​
  //对象:可以改名 推荐使用
  computed: {
    ...mapGetters({
      username:"name",
      list:"shop/list"
    }),
      a(){
      return 10
    }
  },

5.2 mapActions 将仓库的方法成批导入到组件的methods中

//数组:不能改名
methods:{
  ...mapActions([
    "changeWang",
    "changeName",
    "reqArr"
  ]),
    fn(){}
},
​
  //对象:可以改名 推荐使用
  methods:{
    ...mapActions({
      cname:"changeName",
      reqList:"shop/reqList"
    }),
​
  },

6. Vuex规定的规则

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。