状态管理

本章节介绍如何管理数据状态,本项目使用了 Vuex 管理一些全局数据(如用户权限列表、用户名)。

警告

当你不觉得维护数据麻烦的时候,你可以不引入状态管理。因为引入状态管理需要额外学习一些概念和框架,这对你的项目来说可能是不必要的成本。

导语

如果不做处理,系统的状态是零散地分布在许多组件和组件之间的交互中的。

如本项目的表格、表单页面各自存储了自己的数据,侧边栏的伸缩状态在顶部栏、侧边栏、主页面等地方被引用。前者在大型应用中会让系统变得复杂,各个页面的数据会非常多,让你难以管理;后者数据的管理和交互本身就显得复杂。

此时若有一个类似全局变量的东西,可以统一维护这些数据,那我们的管理将变得方便很多(各组件只需要调用、修改这个全局变量即可)。

以下介绍两种状态管理模式:

一、简单Store

当你需要维护少部分全局数据的时候(中小型应用),你可以使用简单的Store模式: 定义一个全局store数据,让各个组件都使用这个数据。这样可以比较好的管理一些全局数据,也能减少一些复杂的组件数据交互。看一下官网的例子:

首先创建一个store(你可以创建一个src/store/index.js文件来维护这个store):

const store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    if (this.debug) console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction () {
    if (this.debug) console.log('clearMessageAction triggered')
    this.state.message = ''
  }
}

export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们约定:

  1. 所有要使用的属性都应该预先在store.state中声明好(避免后续声明的属性不是响应式);
  2. 所有值的修改都要通过storeaction函数来触发,而不能直接通过store.state.xxx = xxx来修改值(这将导致我们跟踪不到哪里修改了值,也不会留下任何操作记录,这对调试来说会是噩梦。而通过action来触发值的变更,我们可以额外做一些处理,如上方的action就打印了log)

然后在src/main.js中将store写入到Vue实例(参考Vue定义全局变量):

import store from 'store/index'
Vue.prototype.store = store
1
2

提示

如果你不是一个Vue CLI应用,只是单纯的引用vue.js,定义一个全局变量var store后就可以直接在js里引用该store全局变量,而不用将store写入Vue实例。

之后你就可以在任意vue组件中通过如下方式来调用、修改message了(注意如果没有组件data引用store.state,则store.state是非响应式的):



 
 
 







 
 
 
 
 



 
 
 
 
 
 
 
 




<template>
  <div>
    <p>全局变量:{{ store.state.message }}(等于共享状态)</p>
    <p>共享状态:{{ sharedState.message }}(等于全局变量)</p>
    <p>私有状态:{{ privateState.message }}</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      // 共享状态数据,指向全局store.state引用,并使其变为响应式,二者同步更新数据
      // 若无此声明,则store.state是非响应式的,即你无法同步其数据变更
      sharedState: this.store.state,
      // 私有状态数据,通过深拷贝复制一份数据,不影响全局store.state
      privateState: JSON.parse(JSON.stringify(store.state))
    }
  },
  methods: {
    changeSharedState (newMessage) {
      // 修改全局变量、共享状态数据:调用store的action
      this.store.setMessageAction(newMessage)
    },
    changePrivateState (newMessage) {
      // 修改私有状态数据:正常赋值
      this.privateState.message = newMessage
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

二、Vuex

当你需要管理一个大型应用数据的时候,以上简单Store模式可能就无法满足你的需求了(比如你的数据需要划分一下模块)。官网提供专为 Vue.js 应用程序开发的状态管理模式——Vuex,它其实就是对以上简单Store模式的扩展,有了更多的功能(如响应式数据、模块划分、time-travel 调试、状态快照导入导出等高级调试功能)。

具体介绍见官网,以下是一些简介。

Vuex由以下5大核心组成:

  1. State:存储数据,唯一数据源;
  2. Getter:store 的计算属性。state中存储了数据,但你可能在很多地方需要对这个数据做同样的处理(如获取某个数组中的指定数据),通过Getter定义一个函数做这个处理,就可以避免大量的重复代码。
// 定义
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

//使用
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. Mutation:更改 Vuex 的 store 中的状态的唯一方法(必须是同步的);
// 定义
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

// 使用
store.commit('increment')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. Action:通过调用Mutation更改 Vuex 的 store 中的状态(可以是异步的
// 定义
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

// 使用
store.dispatch('increment')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. Module:将 store 分割成模块。每个模块拥有自己的 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 的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

项目结构:

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

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。 只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是官方购物车项目结构示例

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14