使用vuex管理状态

vuex简介

vuex是vue应用开发的状态管理模式。vuex可以将数据进行集中管理,把各个组件的共享变量放到一个对象里面进行管理。这样组件在获取共享变量的时候直接访问这个对象即可。例如我们可以将用户的登录信息放到vuex中,这些信息在vuex下是响应式的。

使用vuex

在vue cli中没有安装vuex,因此需要单独安装一下vuex

npm install vuex --save

安装好之后,在src目录下创建一个store文件夹,在该文件夹下创建index.js

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

//安装vuex
Vue.use(Vuex)

//创建对象
const store = new Vuex.Store({
    state: {
        //vuex中添加共享数据
        username: 'paul'
    }
})

//导出store
export default store

main.js中引入上面的store

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

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

在组件中使用vuex中的username数据:

 <!-- 从vuex中获取数据 -->
 <div>{{$store.state.username}}</div>

状态管理模式

vue中提供的状态管理包含下面几个部分:

  • state,驱动应用的数据源,可以认为是data中的属性;
  • view,会将 state 映射到视图,显示不同的信息;
  • actions,用户进行的一些操作,点击,输入等,会导致state发生变化;

注意下图中的虚线箭头,actions会导致state发生变化,state变化会导致view显示的变化。

devtools的使用

devtools是vue官方提供了一个调试工具,便于开发者调试vue程序。首先需要在浏览器中安装,下载链接中的文件:

链接:https://pan.baidu.com/s/1vBVnBcAb_rawypx6X36OzQ
提取码:ld3y

在chrome浏览器中打开拓展程序,然后将上面的文件拖入,重启chrome即可。在chrome中按下f12,就可以看到vue。

在使用devtools调试vuex的时候需要注意,不要直接在组件中修改state,这样会导致devtools无法跟踪,我们需要在组件中通过actions或者mutations来修改state。

将之前vue组件中的内容修改如下,添加方法,通过commit来调用mutations中的方法

<template>
  <div>
      <!-- 从vuex中获取数据 -->
      <div>{{$store.state.username}}</div>
      <div><button @click="change">改名</button></div>
  </div>
</template>
<script>
export default {
    methods: {
      change(){
        //通过commit调用mutations中的changeName方法
        this.$store.commit('changeName')
      }
    }
}
</script>
<style>
</style>

修改vuex中的index.js如下:

![devtools中观察vuex中的数据](F:\testm\19\vue\011\devtools中观察vuex中的数据.png)import Vue from 'vue'
import Vuex from 'vuex'

//安装vuex
Vue.use(Vuex)

//创建对象
const store = new Vuex.Store({
    state: {
        //vuex中添加共享数据
        username: 'paul'
    },
    mutations: {
        //添加方法,vue会将state传入
        changeName(state){
            if('paul' == state.username){
                state.username = 'james'
            }else{
                state.username = 'paul'
            }
        }
    }
})

//导出store
export default store

调整之后可以通过按钮来来改变username的值,此时通过devtools工具中可以看到每次修改的变化。

vuex中的state

在vue官方文档中提到了一个名字单一状态树,意思是在一个项目中只创建一个state对象,将所有的数据管理都交给这一个state对象,这样更加便于管理数据。

vuex中的getters

getters的作用类似于计算属性,当我们需要对state中的数据进行一些计算之后再展示的时候会用到,比如需要在多个组件中展示:欢迎回来,paul。下面利用getters来实现一下,将vuex中的index.js修改为如下内容:

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

//安装vuex
Vue.use(Vuex)

//创建对象
const store = new Vuex.Store({
    state: {
        //vuex中添加共享数据
        username: 'paul'
    },
    mutations: {
        //添加方法,vue会将state传入
        changeName(state) {
            if('paul' == state.username){
                state.username = 'james'
            }else{
                state.username = 'paul'
            }
        }
    },
    getters: {
        //getters中的内容
        welcome(state) {
            return '欢迎回来,' + state.username
        }
    }
})

//导出store
export default store

在组件中使用getters:

<div>{{$store.getters.welcome}}</div>

在getters中的内容会被缓存起来,当数据发生变化的时候才会重新进行计算。

vuex中的mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,这点务必要注意。每个 mutation 都由下面两部分构成:

  • 一个字符串的 事件类型 (type) ,即之前在mutation中定义的changeName
  • 一个 回调函数 (handler),即之前在mutation定义的changeName(state)

提交mutation的方式需要使用commit

this.$store.commit('changeName')

我们在调用commit的时候可以传入第二个参数,这里叫做载荷(payload)。比如我希望在changeName的时候传入额外的参数:

this.$store.commit('changeName','jordan')

mutations中修改如下:

mutations: {
        //将payload传入的值赋值到username中
        changeName(state,name) {
            state.username = name
        }
    }

这里只能传入一个参数,当需要传入多个数据的时候,可以将数据放到一个对象中,将对象传入payload即可。

mutations中的常量

随着代码量的增长,我们很可能会在mutations中添加很多方法,这样就容易在组件中通过commit传入事件类型时,拼写不正确导致问题的出现,该问题可以将方法名写到常量中来解决。在store目录下创建mutation-types.js文件,里面定义常量:

export const CHANGE_NAME = 'changeName'

在store中的index.js里面使用常量:

import Vue from 'vue'
import Vuex from 'vuex'
import { CHANGE_NAME } from './mutation-types'

//安装vuex
Vue.use(Vuex)

//创建对象
const store = new Vuex.Store({
    state: {
        //vuex中添加共享数据
        username: 'paul'
    },
    mutations: {
        //使用常量
        [CHANGE_NAME] (state,name) {
            state.username = name
        }
    },
    getters: {
        welcome(state) {
            return '欢迎回来,' + state.username
        }
    }
})

//导出store
export default store

组件中使用常量:

<template>
  <div>
      <!-- 从vuex中获取数据 -->
      <div>{{$store.state.username}}</div>
      <div>{{$store.getters.welcome}}</div>
      <div><button @click="change">改名</button></div>
  </div>
</template>

<script>
import { CHANGE_NAME } from '@/store/mutation-types';
export default {
    methods: {
      change(){
        //通过commit调用mutations中的changeName方法
        this.$store.commit(CHANGE_NAME,'jordan')
      }
    }
}
</script>

<style>

</style>

这样统一使用常量可以减少低级错误的发生,也可以更直观的观察mutations。

mutations中的函数必须都是同步的

当使用devtool调试mutations的时候,倘若出现了异步操作,此时devtool并不知道什么时候回调函数会被调用,这让状态变得不可追踪,导致devtool中的数据和state中的数据不一致。如果要异步操作的话,需要使用actions。将之前的代码改成使用setTimeout模拟的异步操作,可以看到页面展示的state的值和devtool中state的值不一致

//使用常量
        [CHANGE_NAME] (state,name) {
            //使用setTimeout模拟异步操作
            setTimeout(()=>{
                state.username = name
            },2000)
            
        }

vuex中的actions

actions跟mutations类似,但是两者还是有不同的地方:

  • actions来直接操作mutations,不要通过actions操作state
  • actions中可以包含异步操作

在store的index.js中添加actions,在actions中传入的是context,具体表示的内容等到下面modules中再说明:

import Vue from 'vue'
import Vuex from 'vuex'
import { CHANGE_NAME } from './mutation-types'

//安装vuex
Vue.use(Vuex)

//创建对象
const store = new Vuex.Store({
    state: {
        //vuex中添加共享数据
        username: 'paul'
    },
    mutations: {
        //使用常量
        [CHANGE_NAME] (state,name) {
            state.username = name
        }
    },
    getters: {
        welcome(state) {
            return '欢迎回来,' + state.username
        }
    },
    actions: {
        //这里的context是上下文对象
        changeName (context,name){
            //模拟异步操作
            setTimeout(()=>{
                context.commit(CHANGE_NAME,name)
            },2000)
        }
    }
})

//导出store
export default store

组件中的代码改成调用actions的函数,这样在进行异步操作的时候就不会出现之前的问题了。

<template>
  <div>
      <!-- 从vuex中获取数据 -->
      <div>{{$store.state.username}}</div>
      <div>{{$store.getters.welcome}}</div>
      <div><button @click="change">改名</button></div>
  </div>
</template>

<script>
export default {
    methods: {
      change(){
        //调用action中的函数
        this.$store.dispatch('changeName','jordan')
      }
    }
}
</script>

<style>

</style>

组合actions

actions中的操作是异步的,那么我们如何在异步操作完成之后继续在组件中dispatch后面执行其他操作呢?其实可以在actions中返回一个Promise对象,该对象会返回给调用者,在调用者的后面继续编写then方法就可以解决上述问题了。修改actions代码如下:

actions: {
        //这里的context是上下文对象
        changeName (context,name){
            //将Promise对象返回给调用者
            return new Promise((resolve)=>{
                //模拟异步操作
                setTimeout(()=>{
                    context.commit(CHANGE_NAME,name)
                    //执行resolve之后就会进入到Promise对象的then中
                    resolve()
                },2000)
            })
        }
    }

在组件中获取返回的promise对象,然后继续后面的操作:

 //调用action中的函数
 this.$store.dispatch('changeName','jordan').then(()=>{
 	console.log('回调完成')
 })

vuex中的modules

随着业务变得越来越复杂,在store中编写的内容也会越来越多,而store使用的是单一状态树这样容易导致store臃肿,此时可以在store中定义多个module来分成不同的模块,这样就解决了臃肿的问题了。每个module都有自己的state,getters,mutations,actions。

下面示例代码定义了2个模块user,cart,然后再store中引入了这两个模块

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

//安装vuex
Vue.use(Vuex)

const user = {
    state: {},
    mutations: {},
    getters: {},
    actions: {}
}

const cart = {
    state: {},
    mutations: {},
    getters: {},
    actions: {}
}

//创建对象
const store = new Vuex.Store({
    modules: {
        user: user,
        cart: cart
    }
})

//导出store
export default store

在组件中使用vuex,优先会从store中寻找username,如果没有的话再从modules中寻找:

      <!-- 这里会获取store中的username -->
      <div>{{$store.state.username}}</div>
      <!-- 这里会获取store中模块user的username -->
      <div>{{$store.state.user.username}}</div>

在modules中定义的actions,里面传入了context,这里的context上下文表示的是当前modules,如果是在store中定义的actions,里面传入的context表示当前store。

vuex的项目结构

在大型项目中为了不让index.js显得特别臃肿,我们可以将其中的内容单独抽取到不同的文件中,下面是项目结构示例:

Category: vue