|
| 1 | +# 教程 |
| 2 | + |
| 3 | +让我们通过一个简单的实际例子来理解怎样使用 Vuex。这个例子里,我们要实现一个按钮,当你点击它的时候,计数器会加一。 |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +通过这个简单的例子,我们会解释相应的概念,以及 Vuex 所要解决的问题:如何管理一个包含许多组件的大型应用。假设这个例子使用了以下三个组件: |
| 8 | + |
| 9 | +### `components/App.vue` |
| 10 | + |
| 11 | +根组件,它包含了以下两个子组件: |
| 12 | + |
| 13 | +* `Display` 显示计数器当前的值 |
| 14 | +* `Increment` 使计数器加一的按钮 |
| 15 | + |
| 16 | +```html |
| 17 | +<template> |
| 18 | + <div> |
| 19 | + <Display></Display> |
| 20 | + <Increment></Increment> |
| 21 | + </div> |
| 22 | +</template> |
| 23 | + |
| 24 | +<script> |
| 25 | +
|
| 26 | +import Display from './Display.vue' |
| 27 | +import Increment from './Increment.vue' |
| 28 | +
|
| 29 | +export default { |
| 30 | + components: { |
| 31 | + Display: Display, |
| 32 | + Increment: Increment |
| 33 | + } |
| 34 | +} |
| 35 | +</script> |
| 36 | +``` |
| 37 | + |
| 38 | +### `components/Display.vue` |
| 39 | + |
| 40 | +```html |
| 41 | +<template> |
| 42 | + <div> |
| 43 | + <h3>Count is 0</h3> |
| 44 | + </div> |
| 45 | +</template> |
| 46 | + |
| 47 | +<script> |
| 48 | +export default { |
| 49 | +} |
| 50 | +</script> |
| 51 | +``` |
| 52 | + |
| 53 | +### `components/Increment.vue` |
| 54 | + |
| 55 | +```html |
| 56 | +<template> |
| 57 | + <div> |
| 58 | + <button>Increment +1</button> |
| 59 | + </div> |
| 60 | +</template> |
| 61 | + |
| 62 | +<script> |
| 63 | +export default { |
| 64 | +} |
| 65 | +</script> |
| 66 | +``` |
| 67 | + |
| 68 | +### Vuex 要解决的问题 |
| 69 | + |
| 70 | +* `Increment` 和 `Display` 彼此独立, 不能直接相互传递信息。 |
| 71 | +* `App` 只能通过事件和广播去整合这两个组件。 |
| 72 | +* 因为 `App` 需要整合这两个组件,它们无法被重用,形成了紧密的互相依赖。如果需要重构它,容易导致应用的 bug。 |
| 73 | + |
| 74 | +### Vuex 的流程 |
| 75 | + |
| 76 | +我们需要依次执行这些步骤: |
| 77 | + |
| 78 | + |
| 79 | + |
| 80 | +仅仅为了增加计数采取这么多步骤似乎有点多余。但是,这些概念的引入使得我们在大型应用里可以有效提高可维护性,在长期来看也可以使得 debug 和做后续改进工作变得更容易。那么接下来我们就用 vuex 来改写我们的代码: |
| 81 | + |
| 82 | +### 第一步:加入 store |
| 83 | + |
| 84 | +store 存储应用所需要的所有数据。所有组件都会从 store 中读取数据。在我们开始之前,用 npm 安装 Vuex: |
| 85 | + |
| 86 | +``` |
| 87 | +$ npm install --save vuex |
| 88 | +``` |
| 89 | + |
| 90 | +建一个新文件 `vuex/store.js` |
| 91 | + |
| 92 | +```js |
| 93 | +import Vue from 'vue' |
| 94 | +import Vuex from 'vuex' |
| 95 | + |
| 96 | +// 告诉 vue “使用” vuex |
| 97 | +Vue.use(Vuex) |
| 98 | + |
| 99 | +// 创建一个 object 存储应用启动时的状态 |
| 100 | +const state = { |
| 101 | + // TODO: 设置启动状态 |
| 102 | +} |
| 103 | + |
| 104 | +// 创建一个 object 存储 mutation 函数 |
| 105 | +const mutations = { |
| 106 | + // TODO: set up our mutations |
| 107 | +} |
| 108 | + |
| 109 | +// 通过 new Vuex.Store 结合初始 state 和 mutations,创建 store |
| 110 | +// 这个 store 将和我们的 vue 应用链接起来 |
| 111 | +export default new Vuex.Store({ |
| 112 | + state, |
| 113 | + mutations |
| 114 | +}) |
| 115 | +``` |
| 116 | + |
| 117 | +我们需要修改根组件来让我们的应用和 store 建立联系。 |
| 118 | + |
| 119 | +修改 `components/App.vue`,加上 store. |
| 120 | + |
| 121 | +```js |
| 122 | +import Display from './Display.vue' |
| 123 | +import Increment from './IncrementButton.vue' |
| 124 | +import store from '../vuex/store' // import 我们刚刚创建的 store |
| 125 | + |
| 126 | +export default { |
| 127 | + components: { |
| 128 | + Display: Display, |
| 129 | + Increment: Increment |
| 130 | + }, |
| 131 | + store: store // 在根组件加入 store,让它的子组件和 store 连接 |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +> **提示**: 如果使用 ES6 和 babel 你可以这样写: |
| 136 | +> |
| 137 | +> components: { |
| 138 | +> Display, |
| 139 | +> Increment, |
| 140 | +> }, |
| 141 | +> store |
| 142 | +
|
| 143 | +### 第二步:创建 action |
| 144 | + |
| 145 | +action 是给 component 使用的函数。action 函数能够通过 dispatch 对应的 mutation 函数来触发 store 的更新。action 也可以从后端读取数据之后再触发更新。 |
| 146 | + |
| 147 | +创建一个新文件 `vuex/actions.js`,然后写入一个函数 `incrementCounter`: |
| 148 | + |
| 149 | + |
| 150 | +```js |
| 151 | +// action 会收到 store 作为它的第一个参数 |
| 152 | +// 在 store 里我们只需要 dispatch (在有些情况下需要 state) |
| 153 | +// 我们可以利用 ES6 的解构(destructuring)语法来简化参数的使用 |
| 154 | +export const incrementCounter = function ({ dispatch, state }) { |
| 155 | + dispatch('INCREMENT', 1) |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +然后我们从 `components/Increment.vue` 组件里调用 action 函数 |
| 160 | + |
| 161 | +``` |
| 162 | +<template> |
| 163 | + <div> |
| 164 | + <button @click='increment'>Increment +1</button> |
| 165 | + </div> |
| 166 | +</template> |
| 167 | +
|
| 168 | +<script> |
| 169 | +import { incrementCounter } from '../vuex/actions' |
| 170 | +export default { |
| 171 | + vuex: { |
| 172 | + actions: { |
| 173 | + increment: incrementCounter |
| 174 | + } |
| 175 | + } |
| 176 | +} |
| 177 | +</script> |
| 178 | +``` |
| 179 | + |
| 180 | +回顾一下我们刚刚写的一些有趣的东西: |
| 181 | + |
| 182 | +1. 我们有了一个新的 object `vuex.actions`,包含着一个新的 action。 |
| 183 | +2. 我们没有指定 store, object, state 等等的东西。Vuex 会把它们串联好。 |
| 184 | +3. 我们可以使用 `this.increment()` 在任何方法里调用 action。 |
| 185 | +4. 我们也可以用 `@click` 参数,像使用普通的 Vue 组件方法一样使用它。 |
| 186 | +5. 我们给 action 起名叫 `incrementCounter`,但是在使用时我们可以根据需要重新命名它。 |
| 187 | + |
| 188 | +### 第三步:创建 state 和 mutation |
| 189 | + |
| 190 | +在我们的 `vuex/actions.js` 文件里我们 dispatch 了一个叫做 `INCREMENT` 的 mutation,但是我们还没有写它所对应的具体操作。我们现在就来做这个事。 |
| 191 | + |
| 192 | +修改 `vuex/store.js`: |
| 193 | + |
| 194 | +```js |
| 195 | +const state = { |
| 196 | + // 应用启动时,count 置为0 |
| 197 | + count: 0 |
| 198 | +} |
| 199 | + |
| 200 | +const mutations = { |
| 201 | + // mutation 的第一个参数是当前的 state |
| 202 | + // 你可以在函数里修改 state |
| 203 | + INCREMENT (state, amount) { |
| 204 | + state.count = state.count + amount |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +### 第四步:在组件获取值 |
| 210 | + |
| 211 | +创建一个新的文件 `vuex/getters.js`: |
| 212 | + |
| 213 | +```js |
| 214 | +// 这个 getter 函数会返回 count 的值 |
| 215 | +// 在 ES6 里你可以写成: |
| 216 | +// export const getCount = state => state.count |
| 217 | + |
| 218 | +export function getCount (state) { |
| 219 | + return state.count |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +这个函数返回了 state 对象里我们所需要的部分—— count 的值。我们现在在组件里加入这个 getter 函数。 |
| 224 | + |
| 225 | +修改 `components/Display.vue`: |
| 226 | + |
| 227 | +```html |
| 228 | +<template> |
| 229 | + <div> |
| 230 | + <h3>Count is {{ counterValue }}</h3> |
| 231 | + </div> |
| 232 | +</template> |
| 233 | + |
| 234 | +<script> |
| 235 | +import { getCount } from '../vuex/getters' |
| 236 | +export default { |
| 237 | + vuex: { |
| 238 | + getters: { |
| 239 | + // 注意在这里你需要 `getCount` 函数本身而不是它的执行结果 'getCount()' |
| 240 | + counterValue: getCount |
| 241 | + } |
| 242 | + } |
| 243 | +} |
| 244 | +</script> |
| 245 | +``` |
| 246 | + |
| 247 | +这里我们又加入了一个新的对象 `vuex.getters`。它将 `counterValue` 绑定到了 `getCount` 这个 getter 函数上。我们给它起了一个新名字来使得这个变量在你的组件里表意更明确。 |
| 248 | + |
| 249 | +你可能有点困惑——为什么我们需要用 getter 函数而不是直接从 state 里读取数据。这个概念更多的是一种最佳实践,在大型应用里更加适用。它有这么几种明显的优势: |
| 250 | + |
| 251 | +1. 我们可能需要使用 getter 函数返回经过计算的值(比如总数,平均值等)。 |
| 252 | +2. 在大型应用里,很多组件之间可以复用同一个 getter 函数。 |
| 253 | +3. 如果这个值的位置改变了(比如从 `store.count` 变成了 `store.counter.value`),你只需要改一个 getter 方法,而不是一堆用到它的组件。 |
| 254 | + |
| 255 | +以上便是使用 getter 带来的好处。 |
| 256 | + |
| 257 | +### 第五步:接下来…… |
| 258 | + |
| 259 | +运行一下你的应用,它应该能正常工作了。 |
| 260 | + |
| 261 | +要更深入地理解 Vuex,你可以做一个小练习:尝试对这个应用做一些修改。 |
| 262 | + |
| 263 | +* 加上“减一”的按钮。 |
| 264 | +* 安装 [VueJS Devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd),尝试使用它提供的 Vuex 工具来观察 mutation 对 state 的改动。 |
| 265 | +* 加上一个新的组件,让用户可以在文本框中输入要增加的数值。这个会稍有难度,因为在 vuex 架构中表单的处理有些不同。在开始前可以先读一下[表单处理](forms.md)。 |
0 commit comments