瀏覽代碼

Merge pull request #12 from djyde/doc/zh-cn

翻译了中文文档
Evan You 9 年之前
父節點
當前提交
35df9147ca

+ 1 - 0
docs/LANGS.md

@@ -1 +1,2 @@
 * [English](en/)
+* [简体中文](zh-cn/)

+ 1 - 0
docs/zh-cn/README.md

@@ -0,0 +1 @@
+SUMMARY.md

+ 15 - 0
docs/zh-cn/SUMMARY.md

@@ -0,0 +1,15 @@
+# Table of Contents
+
+- [什么是 Vuex?](intro.md)
+- [核心概念](concepts.md)
+  - [State](state.md)
+  - [Mutations](mutations.md)
+  - [Actions](actions.md)
+- [数据流](data-flow.md)
+- [应用结构](structure.md)
+- [中间件](middlewares.md)
+- [严格模式](strict.md)
+- [表单控制](forms.md)
+- [测试](testing.md)
+- [热重载](hot-reload.md)
+- [API Reference](api.md)

+ 140 - 0
docs/zh-cn/actions.md

@@ -0,0 +1,140 @@
+# Actions
+
+Actions 是用于 dispatch mutations 的函数。Actions 可以是异步的,一个 action 可以 dispatch 多个 mutations.
+
+一个 action 描述了有什么事情应该发生,把本应该在组件中调用的逻辑细节抽象出来。当一个组件需要做某件事时,只需要调用一个 action —— 并不需要关心 到底是 callback 还是一个返回值,因为 actions 会把结果直接反应给 state,state 改变时会触发组件的 DOM 更新 —— 组件完全和 action 要做的事情解耦。
+
+因为,我们通常在 actions 中做 API 相关的请求,并把『组件调用 actions、mutations 被 actions 触发』过程中的异步请求隐藏在其中。
+
+> Vuex actions 和 flux 中的 "actions creators" 是一样的,但是我觉得 flux 这样的定义会让人更疑惑。
+
+### 简单的 Actions
+
+通常一个 action 触发一个 mutation. Vuex 提供一个捷径去定义这样的 actions:
+
+``` js
+const vuex = new Vuex({
+  state: {
+    count: 1
+  },
+  mutations: {
+    INCREMENT (state, x) {
+      state += x
+    }
+  },
+  actions: {
+    // shorthand
+    // 只要提供 mutation 名
+    increment: 'INCREMENT'
+  }
+})
+```
+
+调用 action:
+
+``` js
+vuex.actions.increment(1)
+```
+
+这相当于调用:
+
+``` js
+vuex.dispatch('INCREMENT', 1)
+```
+
+注意所以传递给 action 的参数同样会传递给 mutation handler.
+
+### Thunk Actions
+
+当 actions 里存在逻辑或者异步操作时怎么办?我们可以定义 **Thunks(返回另一个函数的函数) actions**:
+
+``` js
+const vuex = new Vuex({
+  state: {
+    count: 1
+  },
+  mutations: {
+    INCREMENT (state, x) {
+      state += x
+    }
+  },
+  actions: {
+    incrementIfOdd: function (x) {
+      return function (dispatch, state) {
+        if ((state.count + 1) % 2 === 0) {
+          dispatch('INCREMENT', x)
+        }
+      }
+    }
+  }
+})
+```
+
+在这里,外部的函数接受传递进来的参数,之后返回一个带两个参数的函数:第一个参数是 `dispatch` 函数,另一个是 `state`. 我们在这里用 ES2015 语法中的箭头函数简化代码,使其更清晰好看:
+
+``` js
+// ...
+actions: {
+  incrementIfOdd: x => (dispatch, state) => {
+    if ((state.count + 1) % 2 === 0) {
+      dispatch('INCREMENT', x)
+    }
+  }
+}
+```
+
+下面是更简单的语法糖:
+
+``` js
+actions: {
+  increment: 'INCREMENT'
+}
+// ... 相当于:
+actions: {
+  increment: (...args) => dispatch => dispatch('INCREMENT', ...args)
+}
+```
+
+Why don't we just define the actions as simple functions that directly access `vuex.state` and `vuex.dispatch`? The reason is that such usage couples the action functions to the specific vuex instance. By using the thunk syntax, our actions only depend on function arguments and nothing else - this important characteristic makes them easy to test and hot-reloadable!
+
+???
+
+### 异步 Actions
+
+我们能像 thunk 一样定义异步 actions
+
+``` js
+// ...
+actions: {
+  incrementAsync: x => dispatch => {
+    setTimeout(() => {
+      dispatch('INCREMENT', x)
+    }, 1000)
+  }
+}
+```
+
+当在检查购物车时,更好的做法是触发多个不同的 mutations:一个在开始检查购物车时触发,一个在成功后触发,还有一个在失败时触发。
+
+``` js
+// ...
+actions: {
+  checkout: products => (dispatch, state) => {
+    // save the current in cart items
+    const savedCartItems = [...state.cart.added]
+    // send out checkout request, and optimistically
+    // clear the cart
+    dispatch(types.CHECKOUT_REQUEST)
+    // the shop API accepts a success callback and a failure callback
+    shop.buyProducts(
+      products,
+      // handle success
+      () => dispatch(types.CHECKOUT_SUCCESS),
+      // handle failure
+      () => dispatch(types.CHECKOUT_FAILURE, savedCartItems)
+    )
+  }
+}
+```
+
+这样一来,所以需要检查购物车的组件只需要调用 `vuex.actions.checkout(products)`.

+ 95 - 0
docs/zh-cn/api.md

@@ -0,0 +1,95 @@
+# API
+
+### 构造器
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({ ...options })
+```
+
+### 构造器 options
+
+- **state**
+
+  - type: `Object`
+
+    Vuex 实例中 state 对象的根。
+
+    [详细](state.md)
+
+- **mutations**
+
+  - type: `Object | Array<Object>`
+
+    一个以 mutation 名为 key, mutation handler 为 value 的对象。Handler function 接受的第一个参数是 `state` 和在后面的所有 dispatch 传来的参数。
+
+    如果传来一个对象数组,这些对象会自动合并到一个对象中。
+
+    [详细](mutations.md)
+
+- **actions**
+
+  - type: `Object | Array<Object>`
+
+    入口 key 为 action 名的对象,value 可能为
+
+    1. 一个 mutation 名字的 string, 或
+    2. 一个 thunk action 创建函数(thunk action creator function)
+
+    Vuex 会处理这些入口,并创建可以被调用的 action 函数,暴露到实例中的 `actions` 属性。
+
+    如果传来一个对象数组,这些对象会自动合并到一个对象中。
+
+    [详细](actions.md)
+
+- **middlewares**
+
+  - type: `Array<Object>`
+
+    中间件对象的数组。中间件对象是这样的:
+
+    ``` js
+    {
+      snapshot: Boolean, // default: false
+      onInit: Function,
+      onMutation: Function
+    }
+    ```
+
+    所有属性都是可选的. [详细](middlewares.md)
+
+- **strict**
+
+  - type: `Boolean`
+  - default: `false`
+
+    使 Vuex 实例进入严格模式。严格模式中,在 mutation handler 外部对 Vuex state 做任何操作均会抛出错误。
+
+    [详细](strict.md)
+
+### 实例属性
+
+- **state**
+
+  - type: `Object`
+
+    根 state,只读。
+
+- **actions**
+
+  - type: `Object`
+
+    可被调用的 action 函数。
+
+### 实例方法
+
+- **dispatch(mutationName: String, ...args)**
+
+  Directly dispatch a mutation. This is useful in certain situations are in general you should prefer using actions in application code.
+
+  ???
+
+- **hotUpdate(newOptions: Object)**
+
+  热更新新的 actions 和 mutations. [详细](hot-reload.md)

+ 1 - 0
docs/zh-cn/book.json

@@ -0,0 +1 @@
+../book.json

+ 33 - 0
docs/zh-cn/concepts.md

@@ -0,0 +1,33 @@
+# 核心概念
+
+和 Vue 一样,Vuex 暴露一个 `Vuex` 构造,你可以实例化一个 `Vuex 实例`。一般情况下,一个应用中只需要一个 Vuex 实例。你可以把它当成一个『额外的 store』来管理应用状态。
+
+每个 Vuex 实例由三个部分组成:
+
+- **State**: 一个反映应用状态的纯对象。
+
+- **Mutations**: 一些用于改变状态的函数。Mutations **一定是同步的**。
+
+- **Actions**: 一些用于dispatch mutations 的函数。一个 action 可以包含异步操作,并且可以 dispatch 多个 mutations.
+
+我们把 *mutations* 和 *actions* 分开而不是直接用函数去操作 state,原因是我们希望把 mutation 和异步操作分离。许多应用之所以复杂,正是犹豫这两者的耦合。我们解耦两者后,它们都会变得清晰和易于测试。
+
+> 如果你熟悉 Flux,在这里要注意两者的一些差异:Vuex mutations 相当于 Flux 的 **actions**, 而 Vuex 的 actions 相当于 Flux 的 **action creators**.
+
+### 创建一个 Vuex 实例
+
+> **NOTE:** 我们将用 ES2015 语法写接下来的事例代码。如果你还没开始接触 ES2015,那么你应该[现在就开始用](https://babeljs.io/docs/learn-es2015/)! 我们还默认你已经熟悉 [Building Large-Scale Apps with Vue.js](http://vuejs.org/guide/application.html) 中所提到的概念。
+
+创建一个 Vuex 实例非常简单——只需要把以上提到的三个部分放到一起:
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({
+  state: { ... },
+  actions: { ... },
+  mutations: { ... }
+})
+```
+
+实例化后,你就能通过 `vuex.state` 访问 state, 通过 `vuex.actions` 访问 actions。你不能直接访问 mutation functions——你只能通过 actions 或执行 `vuex.disptach()` 访问。接下来我们会更详细地讨论这几个概念。

+ 94 - 0
docs/zh-cn/data-flow.md

@@ -0,0 +1,94 @@
+# 数据流
+
+为了更好地理解 Vuex app 中的数据流,我们来做一个简单的计数器 app。这个例子仅仅用于解释一些概念,实际上你并不需要在这种简单的场合使用 Vuex.
+
+### Setup
+
+``` js
+// vuex.js
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+```
+
+### 定义 App State
+
+``` js
+const state = {
+  count: 0
+}
+```
+
+### 定义会被用到的 State Mutations
+
+``` js
+const mutations = {
+  INCREMENT (state) {
+    state.count++
+  },
+  DECREMENT (state) {
+    state.count--
+  }
+}
+```
+
+### 定义可被调用的 Actions
+
+``` js
+const actions = {
+  increment: 'INCREMENT',
+  decrement: 'DECREMENT'
+}
+```
+
+### 创建 Vuex 实例
+
+``` js
+export default new Vuex({
+  state,
+  mutations,
+  actions
+})
+```
+
+### 在 Vue 组件里使用
+
+**Template**
+
+``` html
+<div>
+  Clicked: {{ count }} times
+  <button v-on:click="increment">+</button>
+  <button v-on:click="decrement">-</button>
+</div>
+```
+
+**Script**
+
+``` js
+import vuex from './vuex.js'
+
+export default {
+  computed: {
+    // 在 computed 属性内绑定 state
+    count () {
+      return vuex.state.count
+    }
+  },
+  methods: {
+    increment: vuex.actions.increment,
+    decrement: vuex.actions.decrement
+  }
+}
+```
+
+你会注意到组件本身非常简洁:它仅仅显示了 Vuex 实例中的一些 state、在用户输入时调用了一些 vuex actions.
+
+You will also notice the data flow is unidirectional, as it should be in Flux:
+
+???
+
+<p align="center">
+  <img width="700px" src="vuex.png">
+</p>

+ 36 - 0
docs/zh-cn/forms.md

@@ -0,0 +1,36 @@
+# 表单控制
+
+当在严格模式中使用 Vuex 时,属于 Vuex 的 state 在使用 `v-model` 时会比较棘手:
+
+``` html
+<input v-model="obj.message">
+```
+
+这里的 `obj` 是在 computed 属性中返回的 Vue state 中的一个对象,在用户输入时,`v-model` 会直接改变 `obj.message`。在严格模式中,因为你只能通过 Vuex mutation handler 改变 state, 所以这里会抛出一个错误。
+
+用 『Vuex 方式』 去解决这个问题的方法是:在 input 中绑定 value,在 `input` 或者 `change` 事件中调用 action:
+
+``` html
+<input :value="obj.message" @input="updateMessage">
+```
+``` js
+// ...
+methods: {
+  updateMessage: function (e) {
+    vuex.actions.updateMessage(e.target.value)
+  }
+}
+```
+
+`updateMessage` action 会 dispatch `'UPDATE_MESSAGE'`, 下面是 mutation handler:
+
+``` js
+// ...
+mutations: {
+  UPDATE_MESSAGE (state, message) {
+    state.obj.message = message
+  }
+}
+```
+
+必须承认,这样做比简单地使用 `v-model` 要啰嗦得多,但这换来的是 state 的改变更加清晰和可被跟踪。实际上,Vuex 并不是想你把所有的 state 都放到 Vuex 实例中去 —— 如果有些 state 你不希望去跟踪,你就应该放在 Vuex 外面(比如组件里面),这样就可以愉快地使用 `v-model` 了。

+ 29 - 0
docs/zh-cn/hot-reload.md

@@ -0,0 +1,29 @@
+# 热重载
+
+Vuex 支持在开发中热重载 actions 和 mutations(使用 Webpack 的 [Hot Module Replacement API](https://webpack.github.io/docs/hot-module-replacement.html))。你也可以在 Browserify 里使用 [browserify-hmr](https://github.com/AgentME/browserify-hmr/) plugin.
+
+只需要简单地调用 `vuex.hotUpdate()`:
+
+``` js
+// ...
+const vuex = new Vuex({
+  state,
+  actions,
+  mutations
+})
+
+if (module.hot) {
+  // 使 actions 和 mutations 成为热重载模块
+  module.hot.accept(['./actions', './mutations'], () => {
+    // require the updated modules
+    // have to add .default here due to babel 6 module output
+    const newActions = require('./actions').default
+    const newMutations = require('./mutations').default
+    // swap in the new actions and mutations  
+    vuex.hotUpdate({
+      actions: newActions,
+      mutations: newMutations
+    })
+  })
+}
+```

+ 13 - 0
docs/zh-cn/intro.md

@@ -0,0 +1,13 @@
+## 什么是 Vuex?
+
+Vuex 是 Vue.js 应用的 state 管理结构。它受启发于 [Flux](https://facebook.github.io/flux/) 和 [Redux](https://github.com/rackt/redux),但 Vuex 的设计更加符合 Vue.js 的响应系统(reactivity system)。
+
+## 我为什么需要它?
+
+当你的应用还很简单的时候,你可能并不需要 Vuex,也不建议在过早地去用它。相反,如果你正在构建一个规模越来越大的 SPA 时,你会开始考虑如何管理好 Vue 组件之外的东西。这就是 Vuex 要做的事。
+
+我们在用 Vue.js 的时候,通常会把 state 储存在组件的内部。也就是说,每一个组件都拥有自己单独的状态。然而有时候我们需要把一部分的 state 共享给其它组件,通常我们都是使用第三方的事件系统去达到这个目的。这个模式的问题在于,事件流会在逐渐增长的组件树中变得复杂,并且在出现问题的时候很难去发现问题的所在。
+
+为了更好的解决在大型应用中共享 state 的问题,我们需要把组件 state(component local state)和应用级 state(application level state)区分开来。应用级的 state 不属于任何特定的组件,但每一个组件仍然可以观察到 state 变化从而自动更新 DOM。通过把 state 管理逻辑放在同一个地方,我们就不需要在各个地方去处理各种事件,因为任何牵扯到多个组件的东西,都会放在这个地方。这样做也能使记录和检查 state 的改变成为可能,甚至可以做些像 time-travel 这样的调试。
+
+即使 Vuex 对状态管理的分离有一些约束,但不会影响你实际代码结构的灵活性。

+ 81 - 0
docs/zh-cn/middlewares.md

@@ -0,0 +1,81 @@
+# 中间件
+
+Vuex 实例可以接受 `middlewares`。中间件给每一个 mutation 暴露勾子(注意和 Redux 的中间件完全没有关系)。一个 Vuex 中间件是一个包含一些勾子函数的简单对象:
+
+``` js
+const myMiddleware = {
+  onInit (state) {
+    // 记录初始 state
+  },
+  onMutation (mutation, state) {
+    // 每个 mutation 后会被调用
+    // mutation 在这里会被格式化为 { type, payload }
+  }
+}
+```
+
+可以这样去使用中间件:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  middlewares: [myMiddleware]
+})
+```
+
+一个中间件默认会接受一个真实的 `state` 对象,但中间件其实用于 debugging, 他们是 **不允许改变 state 的**
+
+有时候中间件想要获得 state 的快照(snapshots),并且用来比较 mutation 前后的 state。这样的中间件必须定义 `snapshot: true` 选项:
+
+``` js
+const myMiddlewareWithSnapshot = {
+  snapshot: true,
+  onMutation (mutation, nextState, prevState) {
+    // nextState and prevState are deep-cloned snapshots
+    // of the state before and after the mutation.
+  }
+}
+```
+
+**可以获得快照的中间件只能在开发环境下使用**。使用 Webpack 或 Browserify 时,我们可以让 build tools 帮我们处理这个事情:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  middlewares: process.env.NODE_ENV !== 'production'
+    ? [myMiddlewareWithSnapshot]
+    : []
+})
+```
+
+中间件默认会被使用。在最终的生产环境下,请根据 [这里](http://vuejs.org/guide/application.html#Deploying_for_Production) 把 `process.env.NODE_ENV !== 'production'` 替换为 `false`。
+
+### 内置的 logger 中间件
+
+Vuex 有个自带的 logger 中间件用于 debugging:
+
+``` js
+const vuex = new Vuex({
+  middlewares: [Vuex.createLogger()]
+})
+```
+
+`createLogger` 函数有这几个 options:
+
+``` js
+const logger = Vuex.createLogger({
+  collapsed: false, // 自动展开输出的 mutations
+  transformer (state) {
+    // 输出前对 state 进行转换
+    // 比如说只返回一个 sub-tree
+    return state.subTree
+  },
+  mutationTransformer (mutation) {
+    // mutations 会格式化为 { type, payload } 输出
+    // 我们可以按照自己的需求格式化成任何我们想要的结构
+    return mutation.type
+  }
+})
+```
+
+注意这个 logger 中间件会得到 state 快照,所以只能用于开发环境。

+ 93 - 0
docs/zh-cn/mutations.md

@@ -0,0 +1,93 @@
+# Mutations
+
+Vuex mutations 是必要的 events:每个 mutation 都有一个 **name** 和 一个 **handler**. Handler 的第一个参数为整个 state 树:
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({
+  state: {
+    count: 1
+  },
+  mutations: {
+    INCREMENT (state) {
+      // 改变 state
+      state.count++
+    }
+  }
+})
+```
+
+用大定命名 mutation 是为了将它和 actions 区分开。
+
+你不能直接调用 mutation handler. 这里的 options 更像是一种事件注册:『当 `INCREMENT` 事件被触发时,调用这个 handler』。引出 mutaion handler 的方法是 dispatch 一个 mutation 事件:
+
+``` js
+vuex.dispatch('INCREMENT')
+```
+
+### 带参数的 dispatch
+
+It is also possible to pass along arguments:
+
+dispatch 可以带参数:
+
+``` js
+// ...
+mutations: {
+  INCREMENT (state, n) {
+    state.count += n
+  }
+}
+```
+``` js
+vuex.dispatch('INCREMENT', 10)
+```
+
+这里的 `10` 会紧跟着 `state` 作为第二个参数被传递到 mutation handler. 和所有额外的参数一样,这些参数被称为 mutation 的 payload.
+
+### 遵守 Vue 响应规则的 mutations
+
+Since Vuex's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue:
+
+1. Prefer initializing your Vuex initial state with all desired fields upfront.
+2. When adding new properties to an Object, you should either use `Vue.set(obj, 'newProp', 123)`, or replace that Object with a fresh one, e.g. `state.obj = { ...state.obj, newProp: 123 }` (Using stage-2 [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) here).
+
+???
+
+### 用常量为 mutation 命名
+
+为了可以使 linters 之类的工具发挥作用,通常我们建议使用常量去命名一个 mutation, 并且把这些常量放在单独的地方。这样做可以让你的代码合作者对整个 app 的 mutations 一目了然:
+
+``` js
+// mutation-types.js
+export const SOME_MUTATION = 'SOME_MUTATION'
+```
+
+``` js
+// vuex.js
+import Vuex from 'vuex'
+import { SOME_MUTATION } from './mutation-types'
+
+const vuex = new Vuex({
+  state: { ... },
+  actions: { ... },
+  mutations: {
+    // we can use the ES2015 computed property name feature
+    // to use a constant as the function name
+    [SOME_MUTATION] (state) {
+      // mutate state
+    }
+  }
+})
+```
+
+用不用常量取决于你 —— 事实上这样做大多数开发者很有帮助。但如果你不喜欢,你完全可以不这样做。
+
+### On to Actions
+
+???
+
+手动调用 `vuex.dispatch` 当然可以,但我们很少在组件里这样做,一般我们会调用 [actions](actions.md)。在 actions 里封装着异步数据请求之类的复杂逻辑。
+
+最后,要记得:所有 mutation handler 必须是 **同步** 的。异步的请求都应在 actions 里。

+ 2 - 0
docs/zh-cn/quickstart.md

@@ -0,0 +1,2 @@
+# Quickstart
+

+ 52 - 0
docs/zh-cn/state.md

@@ -0,0 +1,52 @@
+# State
+
+### 单状态树
+
+Vuex 使用 **单状态树(single state tree)** —— 也就是单个对象包含整个应用的 state。这使得我们在 debugging 的过程中能直接定位相关的 state,快速地取得当前应用的 state 快照(snapshots)。
+
+单状态树和模块化不冲突——在往后的章节里我们会讨论如何将状态管理分离到各个子模块中。
+
+### 在 Vue 组件中获得 Vuex State
+
+和 Vue 实例中的 `data` 对像一样,`state` 对象一旦进入 Vuex 实例,就会变成响应的(powered by [Vue.js 响应系统](http://vuejs.org/guide/reactivity.html))。也就是说,要将 Vuex state 和 Vue 组件联系在一起,只需要简单地将前者返回给后者的 computed 属性。
+
+``` js
+// 一个 Vue 组件模块内部
+
+// 引入一个 vuex 实例
+import vuex from './vuex'
+
+export default {
+  computed: {
+    message () {
+      return vuex.state.message
+    }
+  }
+}
+```
+
+不需要建立任何监听器或者建立组件和 store 之间的联系,你唯一要记住的是,**在 computed 属性里,必须通过 `vuex.state.xxx` 引用 state**。不能在 computed 属性外部存放 state 的引用。
+
+> Flux reference: this can be roughly compared to [`mapStateToProps`](https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) in Redux and [getters](https://optimizely.github.io/nuclear-js/docs/04-getters.html) in NuclearJS.
+
+你会问为什么我们不直接用 `data` 去绑定 vuex state,我们看以下例子:
+
+``` js
+export default {
+  data () {
+    return {
+      message: vuex.state.message
+    }
+  }
+}
+```
+
+因为 `data` 函数不追踪任何 reactive dependencies, 仅仅是一个对 `vuex.state.message` 的静态引用。即使之后 Vuex state 被改变,组件也无法知道它的改变。而 computed 属性不同,它在 state 改变的时候会跟踪所有 reactive dependencies。
+
+### 组件不允许直接改变 state
+
+使用只读的 computed 属性有一个好处就是,能更好的遵守 **组件永远不能直接改变 Vuex state** 的准则。由于我们希望每一个 state 都意图清晰且可被跟踪,所有的 vuex state mutations 都必须在 vuex mutation handlers 当中。
+
+为了践行这条准则,当你在 [Debug 模式](debug.md) 中在 mutation handlers 外部改变 Vuex state 时,Vuex 会抛出错误。
+
+根据这条准则,我们的 Vue 组件处理的事情就很少了:只需要通过只读的 computed 属性和 Vuex state 建立联系,改变 state 的唯一方法是调用 **actions**. 它们照样能在必要时对 state 进行操作,但我们再也不需要在组件里做任何数据获取和 state 相关的逻辑了。

+ 37 - 0
docs/zh-cn/strict.md

@@ -0,0 +1,37 @@
+# Strict Mode
+
+# 严格模式
+
+To enable strict mode, simply pass in `strict: true` when creating the Vuex instance:
+
+要开启严格模式,只需要在创建 Vuex 实例时传入 `strict: true`:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  strict: true
+})
+```
+
+In strict mode, whenever Vuex state is mutated outside of mutation handlers, an error will be thrown. This ensures that all state mutations can be explicitly tracked by debugging tools.
+
+在严格模式中,每当 Vuex state 在 mutation handlers 外部被改变时都会抛出错误,以确保所有 state mutations 都可以清晰地被 debugging 工具跟踪。
+
+### Development vs. Production
+
+### 开发环境 vs. 生产环境
+
+**Do not enable strict mode when deploying for production!** Strict mode runs a deep watch on the state tree for detecting inappropriate mutations - make sure to turn it off in production to avoid the performance cost.
+
+**不要在生产环境中开启严格模式!** 为检测使用有问题的 mutations, 严格模式会对 state 树进行深入的监测。所以为了避免不必要的性能损耗,请在生产环境中关闭严格模式。
+
+Similar to middlewares, we can let the build tools handle that:
+
+和中间件一样,我们可以借助 build tool 来做这件事情:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  strict: process.env.NODE_ENV !== 'production'
+})
+```

+ 139 - 0
docs/zh-cn/structure.md

@@ -0,0 +1,139 @@
+# 应用结构
+
+Vuex 并非限制你的代码结构,而是遵循一些观点:
+
+1. 应用 state 存在于单个对象中
+2. 只有 mutation handlers 可以改变 state
+3. Mutations 必须是同步的,它们做的应该仅仅是改变 state
+4. 所有类似数据获取的异步操作细节都应在 actions 里
+
+Vuex actions 和 mutations 很好的地方在于 **他们都仅仅是函数**。你只需要遵循以上的准则,怎么组织代码就取决于你自己了。最简单的 Vuex 实例甚至只需要在 [单个文件](https://github.com/vuejs/vuex/blob/master/examples/counter/vuex.js) 中声明!然而这对于很多项目来说并不够,所以这里有些根据不同应用规模推荐的不同结构。
+
+### 简单的项目
+
+在简单的项目里,我们可以把 **actions** 和 **mutations** 分离到各自的文件:
+
+``` bash
+.
+├── index.html
+├── main.js
+├── components
+│   ├── App.vue
+│   └── ...
+└── vuex
+    ├── index.js     # exports the vuex instance
+    ├── actions.js   # exports all actions
+    └── mutations.js # exports all mutations
+```
+
+一个真实的 [TodoMVC 例子](https://github.com/vuejs/vuex/tree/master/examples/todomvc).
+
+### Medium to Large Project
+
+### 逐渐复杂的项目
+
+For any non-trivial app, we probably want to further split Vuex-related code into multiple "modules" (roughly comparable to "stores" in vanilla Flux), each dealing with a specific domain of our app. Each module would be managing a sub-tree of the state, exporting the initial state for that sub-tree and all mutations that operate on that sub-tree:
+
+???
+
+``` bash
+├── index.html
+├── main.js
+├── api
+│   └── ... # abstractions for making API requests
+├── components
+│   ├── App.vue
+│   └── ...
+└── vuex
+    ├── actions.js # exports all actions
+    ├── index.js
+    ├── modules
+    │   ├── cart.js       # state and mutations for cart
+    │   └── products.js   # state and mutations for products
+    └── mutation-types.js # constants
+```
+
+一个典型的模块:
+
+``` js
+// vuex/modules/products.js
+import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../mutation-types'
+
+// initial state
+export const productsInitialState = []
+
+// mutations
+export const productsMutations = {
+  [RECEIVE_PRODUCTS] (state, products) {
+    state.products = products
+  },
+
+  [ADD_TO_CART] ({ products }, productId) {
+    const product = products.find(p => p.id === productId)
+    if (product.inventory > 0) {
+      product.inventory--
+    }
+  }
+}
+```
+
+在 `vuex/index.js` 里我们把多个模块集合在一起来创建 Vuex 实例:
+
+``` js
+import Vue from 'vue'
+import Vuex from '../../../src'
+import * as actions from './actions'
+// import parts from modules
+import { cartInitialState, cartMutations } from './modules/cart'
+import { productsInitialState, productsMutations } from './modules/products'
+
+Vue.use(Vuex)
+
+export default new Vuex({
+  // ...
+  // combine sub-trees into root state
+  state: {
+    cart: cartInitialState,
+    products: productsInitialState
+  },
+  // mutations can be an array of mutation definition objects
+  // from multiple modules
+  mutations: [cartMutations, productsMutations]
+})
+```
+
+Since all modules simply export objects and functions, they are quite easy to test and maintain. You are also free to alter the patterns used here to find a structure that fits your preference.
+
+Note that we do not put actions into modules, because a single action may dispatch mutations that affect multiple modules. It's also a good idea to decouple actions from the state shape and the implementation details of mutations for better separation of concerns. If the actions file gets too large, we can turn it into a folder and split out the implementations of long async actions into individual files.
+
+For an actual example, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/master/examples/shopping-cart).
+
+???
+
+### 提取共用的 Computed Getters
+
+在大型项目中,多个组件使用同一个基于 Vuex state 的 computed 属性是有可能的。由于 computed getters 只是普通函数,你可以把它们独立在另一个文件里,以实现共享:
+
+``` js
+// getters.js
+import vuex from './vuex'
+
+export function filteredTodos () {
+  return vuex.state.messages.filter(message => {
+    return message.threadID === vuex.state.currentThreadID
+  })
+}
+```
+
+``` js
+// in a component...
+import { filteredTodos } from './getters'
+
+export default {
+  computed: {
+    filteredTodos
+  }
+}
+```
+
+这非常像 [Getters in NuclearJS](https://optimizely.github.io/nuclear-js/docs/04-getters.html).

+ 137 - 0
docs/zh-cn/testing.md

@@ -0,0 +1,137 @@
+# 测试
+
+Mutations 很容易测试,因为它仅仅是一个带参数的函数。相反测试 Actions 就比较难,因为 actions 会调用其它的一些 APIs. 测试 actions 的时候我们需要做一定的 mocking —— 比如说把 API 调用抽象成一个 service,然后测试的时候用另一个 mock 的 service. 为了方便地 mock,我们可以使用 Webpack 和 [inject-loader](https://github.com/plasticine/inject-loader) 打包测试文件。
+
+If your mutations and actions are written properly, the tests should have no direct dependency on Browser APIs after proper mocking. Thus you can simply bundle the tests with Webpack and run it directly in Node. Alternatively, you can use `mocha-loader` or Karma + `karma-webpack` to run the tests in real browsers.
+
+???
+
+下面是用 Mocha + Chai 测试 mutation 的例子(你可以用任何你喜欢的测试工具):
+
+``` js
+// mutations.js
+export const INCREMENT = state => state.count++
+```
+
+``` js
+// mutations.spec.js
+import { expect } from 'chai'
+import { INCREMENT } from './mutations'
+
+describe('mutations', () => {
+  it('INCREMENT', () => {
+    // mock state
+    const state = { count: 0 }
+    // apply mutation
+    INCREMENT(state)
+    // assert result
+    expect(state.count).to.equal(1)
+  })
+})
+```
+
+测试异步 action 的例子:
+
+``` js
+// actions.js
+import shop from '../api/shop'
+
+export const getAllProducts = () => dispatch => {
+  dispatch('REQUEST_PRODUCTS')
+  shop.getProducts(products => {
+    dispatch('RECEIVE_PRODUCTS', products)
+  })
+}
+```
+
+``` js
+// actions.spec.js
+
+// use require syntax for inline loaders.
+// with inject-loader, this returns a module factory
+// that allows us to inject mocked dependencies.
+import { expect } from 'chai'
+const actionsInjector = require('inject!babel!./actions')
+
+// create the module with our mocks
+const actions = actionsInjector({
+  '../api/shop': {
+    getProducts (cb) {
+      setTimeout(() => {
+        cb([ /* mocked response */ ])
+      }, 100)
+    }
+  }
+})
+
+// helper for testing action with expected mutations
+const testAction = (action, state, expectedMutations, done) => {
+  let count = 0
+  const mockedDispatch = (name, payload) => {
+    const mutation = expectedMutations[count]
+    expect(mutation.name).to.equal(name)
+    if (payload) {
+      expect(mutation.payload).to.deep.equal(payload)
+    }
+    count++
+    if (count >= expectedMutations.length) {
+      done()
+    }
+  }
+  action(mockedDispatch, state)
+}
+
+describe('actions', () => {
+  it('getAllProducts', done => {
+    testAction(actions.getAllProducts(), {}, [
+      { name: 'REQUEST_PRODUCTS' },
+      { name: 'RECEIVE_PRODUCTS', payload: [ /* mocked response */ ] }
+    ], done)
+  })
+})
+```
+
+### 在 Node 中运行
+
+创建以下 webpack 配置:
+
+``` js
+module.exports = {
+  entry: './test.js',
+  output: {
+    path: __dirname,
+    filename: 'test-bundle.js'
+  },
+  module: {
+    loaders: [
+      {
+        test: /\.js$/,
+        loader: 'babel',
+        exclude: /node_modules/
+      }
+    ]
+  },
+  babel: {
+    presets: ['es2015']
+  }
+}
+```
+
+然后:
+
+``` bash
+webpack
+mocha test-bundle.js
+```
+
+### 在浏览器运行
+
+1. Install `mocha-loader`
+2. Change the `entry` from the Webpack config above to `'mocha!babel!./test.js'`.
+3. Start `webpack-dev-server` using the config
+4. Go to `localhost:8080/webpack-dev-server/test-bundle`.
+
+1. 安装 `mocha-loader`
+2. webpack 配置中的 entry 改成 `'mocha!babel!./test.js'`
+3. 用以上配置启动 `webpack-dev-server`
+4. 打开 `localhost:8080/webpack-dev-server/test-bundle`.

二進制
docs/zh-cn/vuex.png