|
1 |
| -# State |
| 1 | +# State 和 Getters |
2 | 2 |
|
3 | 3 | ### 单状态树
|
4 | 4 |
|
5 |
| -Vuex 使用 **单状态树(single state tree)** —— 也就是单个对象包含整个应用的状态。这使得我们能够方便地定位某一部分特定的状态,在 debugging 的过程中也能轻易地取得当前应用的状态快照(snapshots)。 |
| 5 | +Vuex 使用 **单状态树(single state tree)** - 也就是单个对象包含整个应用的状态 and serves as the "single source of truth". This also means usually you will have only one store for each application. 单状态树使得我们能够直截了当地定位某一部分特定的状态,在 debug 的过程中也能轻易地取得当前应用状态的快照(snapshots)。 |
6 | 6 |
|
7 | 7 | 单状态树和模块化并不冲突 —— 在往后的章节里我们会讨论如何将状态管理分离到各个子模块中。
|
8 | 8 |
|
9 |
| -### 在 Vue 组件中获得 Vuex State |
| 9 | +### 在 Vue 组件中获得 Vuex 状态 |
10 | 10 |
|
11 |
| -和 Vue 实例中的 `data` 对像一样,`state` 对象一旦进入 Vuex store,就会被 Vue.js 改造成响应式对象(参见 [Vue.js 响应系统](http://vuejs.org/guide/reactivity.html))。因此,要让 Vuex store 的 state 来驱动 Vue 组件的渲染,只需要简单地在一个计算属性中返回 `store.state` 中需要的部分即可: |
| 11 | +我们如何在我们的 Vue 组件中展示状态呢?由于 Vuex 的状态存储是动态的,从 store 中读取状态最简单的方法就是在计算属性[computed property](http://vuejs.org/guide/computed.html)中返回某个状态: |
12 | 12 |
|
13 | 13 | ``` js
|
14 |
| -// 一个 Vue 组件模块内部 |
| 14 | +// 在 Vue 组件定义时 |
| 15 | +computed: { |
| 16 | + count: function () { |
| 17 | + return store.state.count |
| 18 | + } |
| 19 | +} |
| 20 | +``` |
15 | 21 |
|
16 |
| -// 引入一个 vuex store 实例 |
17 |
| -import store from './store' |
| 22 | +每当 `store.state.count` 变化的时候, 都会使得计算属性重新计算,并且触发相关联的 DOM 更新。 |
18 | 23 |
|
19 |
| -export default { |
20 |
| - computed: { |
21 |
| - message () { |
22 |
| - return store.state.message |
| 24 | +然而,这种模式导致组件依赖全局状态。这导致测试单个组件和多个实例共享一套组件变得困难。在大型应用中,我们也许需要把状态从根组件『注入』到每一个子组件中。下面便是如何实现: |
| 25 | + |
| 26 | +1. 安装 Vuex 并且将您的根组件引入 store: |
| 27 | + |
| 28 | + ``` js |
| 29 | + import Vue from 'vue' |
| 30 | + import Vuex from 'vuex' |
| 31 | + import store from './store' |
| 32 | + import MyComponent from './MyComponent' |
| 33 | + |
| 34 | + // 重要,告诉 Vue 组件如何处理 Vuex 相关的配置 |
| 35 | + |
| 36 | + Vue.use(Vuex) |
| 37 | + |
| 38 | + var app = new Vue({ |
| 39 | + el: '#app', |
| 40 | + // 使用 "store" 来配置,并会在全部的子组件中注入状态 |
| 41 | + store, |
| 42 | + components: { |
| 43 | + MyComponent |
| 44 | + } |
| 45 | + }) |
| 46 | + ``` |
| 47 | + |
| 48 | + 通过在根实例中注册 `store`,store 会注入到根组件下全部的子组件中,并且可以通过 `this.$store` 获得。然而我们很少这么做,因为我们需要真实的引用它。 |
| 49 | + |
| 50 | +2. 在子组件中,在 `vuex.getters` 配置中通过 **getter** 来读取状态: |
| 51 | + |
| 52 | + ``` js |
| 53 | + // MyComponent.js |
| 54 | + export default { |
| 55 | + template: '...', |
| 56 | + data () { ... }, |
| 57 | + // 这里是我们从 store 取回状态的位置 |
| 58 | + vuex: { |
| 59 | + getters: { |
| 60 | + // 一个状态的 getter 函数,将会将 `store.state.count` 绑定为 `this.count` |
| 61 | + count: function (state) { |
| 62 | + return state.count |
| 63 | + } |
| 64 | + } |
23 | 65 | }
|
24 | 66 | }
|
| 67 | + ``` |
| 68 | + |
| 69 | + 请关注特别的 `vuex` 部分。在这里我们指定该组件将会从 store 读取什么状态。对于每一个属性名,我们指定一个 getter 函数,这个只接受一个包含全部状态的 state 状态树作为唯一的参数,然后选择并返回一部分状态,或者根据状态得出的计算属性。返回的结果会像计算属性一样将属性名注入到组件上。 |
| 70 | + |
| 71 | + 大多数情况下,"getter" 函数可以通过 ES2015 的剪头函数来简洁的实现: |
| 72 | + |
| 73 | + ``` js |
| 74 | + vuex: { |
| 75 | + getters: { |
| 76 | + count: state => state.count |
| 77 | + } |
| 78 | + } |
| 79 | + ``` |
| 80 | + |
| 81 | +### getter 函数必须是纯函数 |
| 82 | + |
| 83 | +所有的 Vuex getter 函数,必须是纯函数 [pure functions](https://en.wikipedia.org/wiki/Pure_function) - 他们传入整个状态树,返回一些完全基于传入窗台的值。这让组件更容易测试、组合和更高效。这也意味着 **在 getter 函数中不能依赖 this**。 |
| 84 | + |
| 85 | +如果你确实需要使用 `this`,例如基于组件本身或者传入的状态来计算属性,你可以在计算属性中单独计算这些属性: |
| 86 | + |
| 87 | +``` js |
| 88 | +vuex: { |
| 89 | + getters: { |
| 90 | + currentId: state => state.currentId |
| 91 | + } |
| 92 | +}, |
| 93 | +computed: { |
| 94 | + isCurrent () { |
| 95 | + return this.id === this.currentId |
| 96 | + } |
25 | 97 | }
|
26 | 98 | ```
|
27 | 99 |
|
28 |
| -我们并不需要建立任何监听器或者用一个 API 去建立组件和 store 之间的联系 - Vue 的响应式系统会确保界面的自动更新。唯一要记住的是,**在计算属性里,必须通过 `store.state.xxx` 引用 state**。不要在计算属性外部存放 state 的引用。 |
| 100 | +### getter 函数可以返回派生状态 |
29 | 101 |
|
30 |
| -> Flux 相关:这里计算属性的用法可以和 Redux 的 [`mapStateToProps`](https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) 以及 NuclearJS 的 [getters](https://optimizely.github.io/nuclear-js/docs/04-getters.html) 做类比。 |
| 102 | +Vuex 状态的 getter 函数返回的属性是自动计算的,这就意味着你能够动态(并且高效)地计算派生属性。例如,下面的例子中,我们有一个包含了全部消息的 `messages` 数组,和一个代表用户当前正在浏览的消息索引的 `currentThreadID`。我们想要展示给用户一个根据当前索引过滤后的消息列表: |
| 103 | + |
| 104 | +``` js |
| 105 | +vuex: { |
| 106 | + getters: { |
| 107 | + filteredMessages: state => { |
| 108 | + return state.messages.filter(message => { |
| 109 | + return message.threadID === state.currentThreadID |
| 110 | + }) |
| 111 | + } |
| 112 | + } |
| 113 | +} |
| 114 | +``` |
31 | 115 |
|
32 |
| -你可能会问为什么我们不直接用 `data` 去绑定 state,我们看以下例子: |
| 116 | +因为 Vue.js 计算属性是自动缓存的,且只有一个动态的依赖改变的时候才会重新计算,所以你不必担心 getter 函数会在每次状态树改变的时候调用一次。 |
| 117 | + |
| 118 | +### 在多组件中共享 getter 函数 |
| 119 | + |
| 120 | +正如你所见,getter 函数 `filteredMessages` 在多个组件中都很有用。这种情况下,在多组件中共享 getter 函数就是很好的办法: |
| 121 | + |
| 122 | +``` js |
| 123 | +// getters.js |
| 124 | +export function filteredMessages (state) { |
| 125 | + return state.messages.filter(message => { |
| 126 | + return message.threadID === state.currentThreadID |
| 127 | + }) |
| 128 | +} |
| 129 | +``` |
33 | 130 |
|
34 | 131 | ``` js
|
| 132 | +// 在组件中... |
| 133 | +import { filteredMessages } from './getters' |
| 134 | + |
35 | 135 | export default {
|
36 |
| - data () { |
37 |
| - return { |
38 |
| - message: store.state.message |
| 136 | + vuex: { |
| 137 | + getters: { |
| 138 | + filteredMessages |
39 | 139 | }
|
40 | 140 | }
|
41 | 141 | }
|
42 | 142 | ```
|
43 | 143 |
|
44 |
| -因为 `data` 函数不追踪任何响应式依赖, 我们得到的仅仅是一个对 `store.state.message` 的静态引用。即使之后 state 被改变,组件也无法知道它的改变。而计算属性则不同,它在求值时会自动跟踪依赖,因此当状态改变时会自动触发更新。 |
| 144 | +因为 getter 函数是纯函数,多组件共享的时候 getter 函数可以高效的缓存:当依赖状态改变的时候,在使用 getter 函数的全部组件中只重新计算一次。 |
| 145 | + |
| 146 | +> Flux 参考: Vuex 的 getter 函数可以大致类比成 Redux 中的 [`mapStateToProps`](https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options). 然而, 由于 Vue 的计算可以缓存的计算机制,getter 函数要比 `mapStateToProps` 更加高效,因此更加和 [reselect](https://github.com/reactjs/reselect)相似。 |
45 | 147 |
|
46 |
| -### 组件不允许直接改变 state |
| 148 | +### 组件不允许直接修改 state 中的状态 |
47 | 149 |
|
48 |
| -使用只读的计算属性有一个好处就是,能更好的遵守 **组件不允许直接改变 store state** 的准则。由于我们希望每一次状态的变动都意图清晰且可被跟踪,因此所有的全局状态变动都必须在 store 的 mutation handlers 当中执行。 |
| 150 | +有一点很重要,需要注意,就是**组件永远都不应该直接修改 Vuex store 中的 state 状态**。因为我们想要每个状态的改变都清晰并且可追踪,所有的 Vuex 状态改变必须在 store 中的 mutation 中进行。 |
49 | 151 |
|
50 |
| -为了践行这条准则,在 [严格模式](strict.md) 中如果在 mutation handlers 外部改变 Vuex store state,Vuex 会抛出错误。 |
| 152 | +为了保持这个规则,在严格模式([Strict Mode](strict.md))下,如果 store 中一个状态在 mutation 外进行修改,Vuex 会跑出错误。 |
51 | 153 |
|
52 |
| -根据这条准则,我们的 Vue 组件处理的事情就很少了:它们只需要通过只读的计算属性和 store 内部的 state 建立联系,而改变 state 的唯一方法就是调用 **actions**. 组件依然能在必要时拥有和操作自己的本地状态,但我们再也不需要在组件里包含任何与全局状态相关的逻辑了。 |
| 154 | +有了这一规则,我们的 Vue 组件现在少了很多职能:这势必让 Vuex 中 store 变得只读,组件唯一影响 state 的方法就是触发 **mutations**(我们接下来就讨论)。必要情况下,组件仍然能够拥有和操作自己的状态,但是我们不再在独立的组件中放置任何请求数据或者改变全局状态的逻辑。这些操作全部都集中在 Vuex 相关的文件中,这样能够让大型应用更容易理解和保持规范。 |
0 commit comments