Procházet zdrojové kódy

Fixed symlink problem on Windows

add zh-cn

finish intro.md

finish mutations

finished all translations
jingsam před 8 roky
rodič
revize
eaf53da441

+ 1 - 0
docs/LANGS.md

@@ -1,3 +1,4 @@
 * [2.0 - English](en/)
 * [2.0 - English](en/)
+* [2.0 - 简体中文](zh-cn/)
 * [2.0 - Français](fr/)
 * [2.0 - Français](fr/)
 * [1.0 Docs](old/)
 * [1.0 Docs](old/)

+ 0 - 1
docs/en/README.md

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

+ 22 - 0
docs/en/README.md

@@ -0,0 +1,22 @@
+# Vuex
+
+> Note: This is docs for vuex@2.x.
+
+- [Looking for 1.0 Docs?](https://github.com/vuejs/vuex/tree/1.0/docs)
+- [Release Notes](https://github.com/vuejs/vuex/releases)
+- [Installation](installation.md)
+- [What is Vuex?](intro.md)
+- [Getting Started](getting-started.md)
+- Core Concepts
+  - [State](state.md)
+  - [Getters](getters.md)
+  - [Mutations](mutations.md)
+  - [Actions](actions.md)
+  - [Modules](modules.md)
+- [Application Structure](structure.md)
+- [Plugins](plugins.md)
+- [Strict Mode](strict.md)
+- [Form Handling](forms.md)
+- [Testing](testing.md)
+- [Hot Reloading](hot-reload.md)
+- [API Reference](api.md)

+ 0 - 1
docs/en/book.json

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

+ 19 - 0
docs/en/book.json

@@ -0,0 +1,19 @@
+{
+  "gitbook": "2.x.x",
+  "plugins": ["edit-link", "prism", "-highlight", "github"],
+  "pluginsConfig": {
+    "edit-link": {
+      "base": "https://github.com/vuejs/vuex/tree/dev/docs",
+      "label": "Edit This Page"
+    },
+    "github": {
+      "url": "https://github.com/vuejs/vuex/"
+    }
+  },
+  "links": {
+    "sharing": {
+      "facebook": false,
+      "twitter": false
+    }
+  }
+}

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

@@ -0,0 +1,22 @@
+# Vuex
+
+> 注意:本文档针对的是 vuex@2.x。
+
+- [寻找 1.0 文档?](https://github.com/vuejs/vuex/tree/1.0/docs)
+- [更新记录](https://github.com/vuejs/vuex/releases)
+- [安装](installation.md)
+- [Vuex 是什么?](intro.md)
+- [开始](getting-started.md)
+- 核心概念
+  - [State](state.md)
+  - [Getters](getters.md)
+  - [Mutations](mutations.md)
+  - [Actions](actions.md)
+  - [Modules](modules.md)
+- [项目结构](structure.md)
+- [插件](plugins.md)
+- [严格模式](strict.md)
+- [表单处理](forms.md)
+- [测试](testing.md)
+- [热重载](hot-reload.md)
+- [API 文档](api.md)

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

@@ -0,0 +1,22 @@
+# Vuex
+
+> 注意:本文档针对的是 vuex@2.x。
+
+- [寻找 1.0 文档?](https://github.com/vuejs/vuex/tree/1.0/docs)
+- [更新记录](https://github.com/vuejs/vuex/releases)
+- [安装](installation.md)
+- [Vuex 是什么?](intro.md)
+- [开始](getting-started.md)
+- 核心概念
+  - [State](state.md)
+  - [Getters](getters.md)
+  - [Mutations](mutations.md)
+  - [Actions](actions.md)
+  - [Modules](modules.md)
+- [项目结构](structure.md)
+- [插件](plugins.md)
+- [严格模式](strict.md)
+- [表单处理](forms.md)
+- [测试](testing.md)
+- [热重载](hot-reload.md)
+- [API 文档](api.md)

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

@@ -0,0 +1,174 @@
+# Actions
+
+Action 类似于 mutation,不同在于:
+
+- Action 提交的是 mutation,而不是直接变更状态。
+- Action 可以包含任意异步操作。
+
+让我们来注册一个简单的 action:
+
+``` js
+const store = new Vuex.Store({
+  state: {
+    count: 0
+  },
+  mutations: {
+    increment (state) {
+      state.count++
+    }
+  },
+  actions: {
+    increment (context) {
+      context.commit('increment')
+    }
+  }
+})
+```
+
+Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 `context.commit` 提交一个 mutation,或者通过 `context.state` 和 `context.getters` 来获取 state 和 getters。当我们在之后介绍到 [Modules](modules.md) 时,你就知道 context 对象为什么不是 store 实例本身了。
+
+实践中,我们会经常会用到 ES2015 的 [参数解构](https://github.com/lukehoban/es6features#destructuring) 来简化代码(特别是我们需要调用 `commit` 很多次的时候):
+
+``` js
+actions: {
+  increment ({ commit }) {
+    commit('increment')
+  }
+}
+```
+
+### 分发 Action
+
+Action 通过 `store.dispatch` 方法触发:
+
+``` js
+store.dispatch('increment')
+```
+
+乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 **mutation 必须同步执行**这个限制么?Action 就不受约束!我们可以在 action 内部执行**异步**操作:
+
+``` js
+actions: {
+  incrementAsync ({ commit }) {
+    setTimeout(() => {
+      commit('increment')
+    }, 1000)
+  }
+}
+```
+
+Actions 支持同样的载荷方式和对象方式进行分发:
+
+``` js
+// 以载荷形式分发
+store.dispatch('incrementAsync', {
+  amount: 10
+})
+
+// 以对象形式分发
+store.dispatch({
+  type: 'incrementAsync',
+  amount: 10
+})
+```
+
+来看一个更加实际的购物车示例,涉及到**调用异步 API** 和 **分发多重 mutations**:
+
+``` js
+actions: {
+  checkout ({ commit, state }, payload) {
+    // 把当前购物车的物品备份起来
+    const savedCartItems = [...state.cart.added]
+    // 发出结账请求,然后乐观地清空购物车
+    commit(types.CHECKOUT_REQUEST)
+    // 购物 API 接受一个成功回调和一个失败回调
+    shop.buyProducts(
+      products,
+      // 成功操作
+      () => commit(types.CHECKOUT_SUCCESS),
+      // 失败操作
+      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
+    )
+  }
+}
+```
+
+注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。
+
+### 在组件中分发 Action
+
+你在组件中使用 `this.$store.dispatch('xxx')` 分发 action,或者使用 `mapActions` 辅助函数将组件的 methods 映射为 `store.dispatch` 调用(需要先在根节点注入 `store`):
+
+``` js
+import { mapActions } from 'vuex'
+
+export default {
+  // ...
+  methods: {
+    ...mapActions([
+      'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
+    ]),
+    ...mapActions({
+      add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
+    })
+  }
+}
+```
+
+### 组合 Actions
+
+Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
+
+第一件事你需要清楚的是 `store.dispatch` 的返回的是被触发的 action 函数的返回值,因此你可以在 action 中返回 Promise:
+
+``` js
+actions: {
+  actionA ({ commit }) {
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        commit('someMutation')
+        resolve()
+      }, 1000)
+    })
+  }
+}
+```
+
+现在你可以:
+
+``` js
+store.dispatch('actionA').then(() => {
+  // ...
+})
+```
+
+在另外一个 action 中也可以:
+
+``` js
+actions: {
+  // ...
+  actionB ({ dispatch, commit }) {
+    return dispatch('actionA').then(() => {
+      commit('someOtherMutation')
+    })
+  }
+}
+```
+
+最后,如果我们利用 [async / await](https://tc39.github.io/ecmascript-asyncawait/) 这个 JavaScript 即将到来的新特性,我们可以像这样组合 action:
+
+``` js
+// 假设 getData() 和 getOtherData() 返回的是 Promise
+
+actions: {
+  async actionA ({ commit }) {
+    commit('gotData', await getData())
+  },
+  async actionB ({ dispatch, commit }) {
+    await dispatch('actionA') // 等待 actionA 完成
+    commit('gotOtherData', await getOtherData())
+  }
+}
+```
+
+> 一个 `store.dispatch` 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

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

@@ -0,0 +1,178 @@
+# API 参考
+
+### Vuex.Store
+
+``` js
+import Vuex from 'vuex'
+
+const store = new Vuex.Store({ ...options })
+```
+
+### Vuex.Store 构造器选项
+
+- **state**
+
+  - 类型: `Object`
+
+    Vuex store 实例的根 state 对象。
+
+    [详细介绍](state.md)
+
+- **mutations**
+
+  - 类型: `{ [type: string]: Function }`
+
+    在 store 上注册 mutation,处理函数总是接受 `state` 作为第一个参数(如果定义在模块中,则为模块的本地状态),`payload` 作为第二个参数(可选)。
+
+    [详细介绍](mutations.md)
+
+- **actions**
+
+  - 类型: `{ [type: string]: Function }`
+
+    在 store 上注册 action。处理函数接受一个 `context` 对象,包含以下属性:
+
+    ``` js
+    {
+      state,     // 等同于 store.state, 若在模块中则为本地状态
+      rootState, // 等同于 store.state, 只存在于模块中
+      commit,    // 等同于 store.commit
+      dispatch,  // 等同于 store.dispatch
+      getters    // 等同于 store.getters
+    }
+    ```
+
+    [详细介绍](actions.md)
+
+- **getters**
+
+  - 类型: `{ [key: string]: Function }`
+
+  在 store 上注册 getter,getter 方法接受以下参数:
+    
+    ```
+    state,     // 如果在模块中定义则为模块的本地状态
+    getters,   // 等同于 store.getters
+    rootState  // 等同于 store.state
+    ```
+    注册的 getter 暴露为 `store.getters`。
+
+    [详细介绍](getters.md)
+
+- **modules**
+
+  - 类型: `Object`
+
+    包含了子模块的对象,会被合并到 store,大概长这样:
+
+    ``` js
+    {
+      key: {
+        state,
+        mutations,
+        actions?,
+        getters?,
+        modules?
+      },
+      ...
+    }
+    ```
+
+    与根模块的选项一样,每个模块也包含 `state` 和 `mutations` 选项。模块的状态使用 key 关联到 store 的根状态。模块的 mutation 和 getter 只会接收 module 的本地状态作为第一个参数,而不是根状态,并且模块 action 的 `context.state` 同样指向本地状态。
+
+    [详细介绍](modules.md)
+
+- **plugins**
+
+  - 类型: `Array<Function>`
+
+    一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数,可以监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)
+
+    [详细介绍](plugins.md)
+
+- **strict**
+
+  - 类型: `Boolean`
+  - 默认值: `false`
+
+    使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。
+
+    [详细介绍](strict.md)
+
+### Vuex.Store 实例属性
+
+- **state**
+
+  - 类型: `Object`
+
+    根状态,只读。
+
+- **getters**
+
+  - 类型: `Object`
+
+    暴露出注册的 getter,只读。
+
+### Vuex.Store 实例方法
+
+- **`commit(type: string, payload?: any) | commit(mutation: Object)`**
+
+  提交 mutation。 [详细介绍](mutations.md)
+
+- **`dispatch(type: string, payload?: any) | dispatch(action: Object)`**
+
+  分发 action。返回 action 方法的返回值,如果多个处理函数被触发,那么返回一个 Pormise。 [详细介绍](actions.md)
+
+
+- **`replaceState(state: Object)`**
+
+  替换 store 的根状态,仅用状态合并或 time-travel 调试。
+
+- **`watch(getter: Function, cb: Function, options?: Object)`**
+
+  响应式地监测一个 getter 方法的返回值,当值改变时调用回调函数。getter 接收 store 的状态作为唯一参数。接收一个可选的对象参数表示 Vue 的 `vm.$watch` 方法的参数。
+
+  要停止监测,直接调用返回的处理函数。
+
+- **`subscribe(handler: Function)`**
+
+  注册监听 store 的 mutation。`handler` 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数:
+
+  ``` js
+  store.subscribe((mutation, state) => {
+    console.log(mutation.类型)
+    console.log(mutation.payload)
+  })
+  ```
+
+  通常用于插件。 [详细介绍](plugins.md)
+
+- **`registerModule(path: string | Array<string>, module: Module)`**
+
+  注册一个动态模块。 [详细介绍](modules.md#dynamic-module-registration)
+
+- **`unregisterModule(path: string | Array<string>)`**
+
+  卸载一个动态模块。 [详细介绍](modules.md#dynamic-module-registration)
+
+- **`hotUpdate(newOptions: Object)`**
+
+  热替换新的 action 和 mutation。 [详细介绍](hot-reload.md)
+
+### 组件绑定的辅助函数
+
+- **`mapState(map: Array<string> | Object): Object`**
+
+  创建组件的计算属性返回 Vuex store 中的状态。 [详细介绍](state.md#the-mapstate-helper)
+
+- **`mapGetters(map: Array<string> | Object): Object`**
+
+  创建组件的计算属性返回 getter 的返回值。 [详细介绍](getters.md#the-mapgetters-helper)
+
+- **`mapActions(map: Array<string> | Object): Object`**
+
+  创建组件方法分发 action。 [详细介绍](actions.md#dispatching-actions-in-components)
+
+- **`mapMutations(map: Array<string> | Object): Object`**
+
+  创建组件方法提交 mutation。 [详细介绍](mutations.md#commiting-mutations-in-components)

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

@@ -0,0 +1,19 @@
+{
+  "gitbook": "2.x.x",
+  "plugins": ["edit-link", "prism", "-highlight", "github"],
+  "pluginsConfig": {
+    "edit-link": {
+      "base": "https://github.com/vuejs/vuex/tree/dev/docs",
+      "label": "编辑此页面"
+    },
+    "github": {
+      "url": "https://github.com/vuejs/vuex/"
+    }
+  },
+  "links": {
+    "sharing": {
+      "facebook": false,
+      "twitter": false
+    }
+  }
+}

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

@@ -0,0 +1,58 @@
+# 表单处理
+
+当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 `v-model` 会比较棘手:
+
+``` html
+<input v-model="obj.message">
+```
+
+假设这里的 `obj` 是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,`v-model` 会试图直接修改 `obj.message`。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
+
+用『Vuex 的思维』去解决这个问题的方法是:给 `<input>` 中绑定 value,然后侦听 `input` 或者 `change` 事件,在事件回调中调用 action:
+
+``` html
+<input :value="message" @input="updateMessage">
+```
+``` js
+// ...
+computed: {
+  ...mapState({
+    message: state => state.obj.message
+  })
+},
+methods: {
+  updateMessage (e) {
+    this.$store.commit('updateMessage', e.target.value)
+  }
+}
+```
+
+下面是 mutation 函数:
+
+``` js
+// ...
+mutations: {
+  updateMessage (state, message) {
+    state.obj.message = message
+  }
+}
+```
+
+### 双向绑定的计算属性
+
+必须承认,这样做比简单地使用“`v-model` + 本地状态”要啰嗦得多,并且也损失了一些 `v-model` 中很有用的特性。另一个方法是使用带有 setter 的双向绑定计算属性:
+
+``` js
+// ...
+computed: {
+  message: {
+    get () {
+      return this.$store.state.obj.message
+    },
+    set (value) {
+      this.$store.commit('updateMessage', value)
+    }
+  }
+}
+```
+

+ 92 - 0
docs/zh-cn/getters.md

@@ -0,0 +1,92 @@
+
+# Getters
+
+有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:
+
+``` js
+computed: {
+  doneTodosCount () {
+    return this.$store.state.todos.filter(todo => todo.done).length
+  }
+}
+```
+
+如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它 —— 无论哪种方式都不是很理想。
+
+Vuex 允许我们在 store 中定义『getters』(可以认为是 store 的计算属性)。Getters 接受 state 作为其第一个参数:
+
+``` js
+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)
+    }
+  }
+})
+```
+
+Getters 会暴露为 `store.getters` 对象:
+
+``` js
+store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
+```
+
+Getters 也可以接受其他 getters 作为第二个参数:
+
+``` js
+getters: {
+  // ...
+  doneTodosCount: (state, getters) => {
+    return getters.doneTodos.length
+  }
+}
+```
+
+``` js
+store.getters.doneTodosCount // -> 1
+```
+
+我们可以很容易地在任何组件中使用它:
+
+``` js
+computed: {
+  doneTodosCount () {
+    return this.$store.getters.doneTodosCount
+  }
+}
+```
+
+### `mapGetters` 辅助函数
+
+`mapGetters` 辅助函数仅仅是将 store 中的 getters 映射到 本地计算属性:
+
+``` js
+import { mapGetters } from 'vuex'
+
+export default {
+  // ...
+  computed: {
+  // 使用对象展开运算符将 getters 混入 computed 对象中
+    ...mapGetters([
+      'doneTodosCount',
+      'anotherGetter',
+      // ...
+    ])
+  }
+}
+```
+
+如果你想将一个 getter 属性另取一个名字,使用对象形式:
+
+``` js
+mapGetters({
+  // 映射 this.doneCount 为 store.getters.doneTodosCount
+  doneCount: 'doneTodosCount'
+})
+```

+ 44 - 0
docs/zh-cn/getting-started.md

@@ -0,0 +1,44 @@
+# 开始
+
+每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态(即 state)。Vuex 和单纯的全局对象有以下两点不同:
+
+1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
+
+2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地**提交 mutations**。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
+
+### 最简单的 Store
+
+> **提示:**我们将在后续的文档示例代码中使用 ES2015 语法。如果你还没能掌握 ES2015,[你得抓紧了](https://babeljs.io/docs/learn-es2015/)!
+
+[安装](installation.md) Vuex 之后,让我们来创建一个 store。创建过程直截了当——仅需要提供一个初始 state 对象和一些 mutations:
+
+``` js
+// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  state: {
+    count: 0
+  },
+  mutations: {
+    increment (state) {
+      state.count++
+    }
+  }
+})
+```
+
+现在,你可以通过 `store.state` 来获取状态对象,以及通过 `store.commit` 方法触发状态变更:
+
+``` js
+store.commit('increment')
+
+console.log(store.state.count) // -> 1
+```
+
+再次强调,我们通过提交 mutation 的方式,而非直接改变 `store.state.count`,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。
+
+由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutations。
+
+这是一个[最基本的 Vuex 记数应用](https://jsfiddle.net/yyx990803/n9jmu5v7/)示例。
+
+接下来,我们将会更深入地探讨一些核心概念。让我们先从 [State](state.md) 概念开始。

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

@@ -0,0 +1,44 @@
+# 热重载
+
+使用 webpack 的 [Hot Module Replacement API](https://webpack.github.io/docs/hot-module-replacement.html),Vuex 支持在开发过程中热重载 mutation、modules、actions、和getters。你也可以在 Browserify 中使用 [browserify-hmr](https://github.com/AgentME/browserify-hmr/) 插件。
+
+对于 mutation 和模块,你需要使用 `store.hotUpdate()` 方法:
+
+``` js
+// store.js
+import Vue from 'vue'
+import Vuex from 'vuex'
+import mutations from './mutations'
+import moduleA from './modules/a'
+
+Vue.use(Vuex)
+
+const state = { ... }
+
+const store = new Vuex.Store({
+  state,
+  mutations,
+  modules: {
+    a: moduleA
+  }
+})
+
+if (module.hot) {
+  // 使 actions 和 mutations 成为可热重载模块
+  module.hot.accept(['./mutations', './modules/a'], () => {
+    // 获取更新后的模块
+    // 因为 babel 6 的模块编译格式问题,这里需要加上 .default
+    const newMutations = require('./mutations').default
+    const newModuleA = require('./modules/a').default
+    // 加载新模块 
+    store.hotUpdate({
+      mutations: newMutations,
+      modules: {
+        a: newModuleA
+      }
+    })
+  })
+}
+```
+
+参考热重载示例 [counter-hot](https://github.com/vuejs/vuex/tree/dev/examples/counter-hot)。

binární
docs/zh-cn/images/flow.png


binární
docs/zh-cn/images/vuex.png


+ 42 - 0
docs/zh-cn/installation.md

@@ -0,0 +1,42 @@
+# 安装
+
+### 直接下载 / CDN 引用
+
+[https://unpkg.com/vuex](https://unpkg.com/vuex)
+
+[Unpkg.com](https://unpkg.com) 提供了基于 NPM 的 CDN 链接。以上的链接会一直指向 NPM 上发布的最新版本。您也可以通过 `https://unpkg.com/vuex@2.0.0` 这样的方式指定特定的版本。
+
+在 Vue 之后引入 `vuex` 会进行自动安装:
+
+``` html
+<script src="/path/to/vue.js"></script>
+<script src="/path/to/vuex.js"></script>
+```
+
+### NPM
+
+``` bash
+npm install vuex
+```
+
+在一个模块化的打包系统中,您必须显式地通过 `Vue.use()` 来安装 Vuex:
+
+``` js
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+```
+
+当使用全局 script 标签引用 Vuex 时,不需要以上安装过程。
+
+### 自己构建
+
+如果需要使用 dev 分支下的最新版本,您可以直接从 GitHub 上克隆代码并自己构建。
+
+``` bash
+git clone https://github.com/vuejs/vuex.git node_modules/vuex
+cd node_modules/vuex
+npm install
+npm run build
+```

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

@@ -0,0 +1,63 @@
+# Vuex 是什么?
+
+Vuex 是一个专为 Vue.js 应用程序开发的**状态管理模式**。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 [devtools extension](https://github.com/vuejs/vue-devtools),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
+
+### 什么是“状态管理模式”?
+
+让我们从一个简单的 Vue 计数应用开始:
+
+``` js
+new Vue({
+  // state
+  data () {
+    return {
+      count: 0
+    }
+  },
+  // view
+  template: `
+    <div>{{ count }}</div>
+  `,
+  // actions
+  methods: {
+    increment () {
+      this.count++
+    }
+  }
+})
+```
+
+这个状态自管理应用包含以下几个部分:
+
+- **state**,驱动应用的数据源;
+- **view**,以声明方式将**state**映射到视图;
+- **actions**,响应在**view**上的用户输入导致的状态变化。
+
+以下是一个表示“单向数据流”理念的极简示意:
+
+<p style="text-align: center; margin: 2em">
+  <img style="max-width:450px;" src="./images/flow.png">
+</p>
+
+但是,当我们的应用遇到**多个组件共享状态**时,单向数据流的简洁性很容易被破坏:
+
+- 多个视图依赖于同一状态。
+- 来自不同视图的行为需要变更同一状态。
+
+对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
+
+因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者出发行为!
+
+另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。
+
+这就是 Vuex 背后的基本思想,借鉴了 [Flux](https://facebook.github.io/flux/docs/overview.html)、[Redux](http://redux.js.org/)、和 [The Elm Architecture](https://guide.elm-lang.org/architecture/)。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
+
+![vuex](./images/vuex.png)
+
+### 什么情况下我应该使用 Vuex?
+
+虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
+
+如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 [global event bus](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication) 就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
+
+> Flux 架构就像眼镜:您自会知道什么时候需要它。

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

@@ -0,0 +1,137 @@
+# Modules
+
+使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。
+
+为了解决以上问题,Vuex 运行我们将 store 分割到**模块(module)**。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割:
+
+``` js
+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 的状态
+```
+
+### 模块的本地状态
+
+对于模块内部的 mutation 和 getter,接收的第一个参数是**模块的本地状态**。
+
+``` js
+const moduleA = {
+  state: { count: 0 },
+  mutations: {
+    increment: (state) {
+      // state 模块的本地状态
+      state.count++
+    }
+  },
+
+  getters: {
+    doubleCount (state) {
+      return state.count * 2
+    }
+  }
+}
+```
+
+同样,对于模块内部的 action,`context.state` 是本地状态,根节点的状态是 `context.rootState`:
+
+``` js
+const moduleA = {
+  // ...
+  actions: {
+    incrementIfOdd ({ state, commit }) {
+      if (state.count % 2 === 1) {
+        commit('increment')
+      }
+    }
+  }
+}
+```
+
+对于模块内部的 getter,根节点状态会作为第三个参数:
+
+``` js
+const moduleA = {
+  // ...
+  getters: {
+    sumWithRootCount (state, getters, rootState) {
+      return state.count + rootState.count
+    }
+  }
+}
+```
+
+### 命名空间
+
+模块内部的 action、mutation、和 getter 现在仍然注册在**全局命名空间**——这样保证了多个模块能够响应同一 mutation 或 action。你可以通过添加前缀或后缀的方式隔离各模块,以避免名称冲突。你也可能希望写出一个可复用的模块,其使用环境不可控。例如,我们想创建一个 `todos` 模块:
+
+``` js
+// types.js
+
+// 定义 getter、action、和 mutation 的名称为常量,以模块名 `todos` 为前缀
+export const DONE_COUNT = 'todos/DONE_COUNT'
+export const FETCH_ALL = 'todos/FETCH_ALL'
+export const TOGGLE_DONE = 'todos/TOGGLE_DONE'
+```
+
+``` js
+// modules/todos.js
+import * as types from '../types'
+
+// 实用添加了前缀的名称定义 getter、action 和 mutation
+const todosModule = {
+  state: { todos: [] },
+
+  getters: {
+    [types.DONE_COUNT] (state) {
+      // ...
+    }
+  },
+
+  actions: {
+    [types.FETCH_ALL] (context, payload) {
+      // ...
+    }
+  },
+
+  mutations: {
+    [types.TOGGLE_DONE] (state, payload) {
+      // ...
+    }
+  }
+}
+```
+
+### 模块动态注册
+
+在 store 创建**之后**,你可以使用 `store.registerModule` 方法注册模块:
+
+``` js
+store.registerModule('myModule', {
+  // ...
+})
+```
+
+模块的状态将是 `store.state.myModule`。
+
+模块动态注册功能可以让其他 Vue 插件为了应用的 store 附加新模块,以此来分割 Vuex 的状态管理。例如,[`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) 插件可以集成 vue-router 与 vuex,管理动态模块的路由状态。
+
+你也可以使用 `store.unregisterModule(moduleName)` 动态地卸载模块。注意,你不能使用此方法卸载静态模块(在创建 store 时声明的模块)。

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

@@ -0,0 +1,184 @@
+# Mutations
+
+更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 **事件类型 (type)** 和 一个 **回调函数 (handler)**。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
+
+``` js
+const store = new Vuex.Store({
+  state: {
+    count: 1
+  },
+  mutations: {
+    increment (state) {
+      // 变更状态
+      state.count++
+    }
+  }
+})
+```
+
+你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 `increment` 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 **store.commit** 方法:
+
+``` js
+store.commit('increment')
+```
+
+### 提交载荷(Playload)
+
+你可以向 `store.commit` 传入额外的参数,即 mutation 的 **载荷(playload)**:
+
+``` js
+// ...
+mutations: {
+  increment (state, n) {
+    state.count += n
+  }
+}
+```
+``` js
+store.commit('increment', 10)
+```
+
+在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
+
+``` js
+// ...
+mutations: {
+  increment (state, payload) {
+    state.count += payload.amount
+  }
+}
+```
+``` js
+store.commit('increment', {
+  amount: 10
+})
+```
+
+### 对象风格的提交方式
+
+提交 mutation 的另一种方式是直接使用包含 `type` 属性的对象:
+
+``` js
+store.commit({
+  type: 'increment',
+  amount: 10
+})
+```
+
+当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
+
+``` js
+mutations: {
+  increment (state, payload) {
+    state.count += payload.amount
+  }
+}
+```
+
+### 静默提交
+
+> 注意:当我们在 devtools 中实现了 mutation 过滤功能后,此项功能将会被废弃。
+
+默认情况下,每个提交的 mutation 都会发送至插件(如 devtools)。但是在有些场景下,你可能不希望插件去记录每一个状态变更。在一个很短时间内向 store 的多个提交不总是需要被追踪的。在这种情况下,你可以向 `store.commit` 传第三个参数来对插件静默该 mutation:
+
+``` js
+store.commit('increment', {
+  amount: 1
+}, { silent: true })
+
+// 使用对象风格方式提交
+store.commit({
+  type: 'increment',
+  amount: 1
+}, { silent: true })
+```
+
+### Mutations 需遵守 Vue 的响应规则
+
+既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
+
+1. 最好提前在你的 store 中初始化好所有所需属性。
+
+2. 当需要在对象上添加新属性时,你应该
+
+  - 使用 `Vue.set(obj, 'newProp', 123)`, 或者 -
+
+  - 以新对象替换老对象。例如,利用 stage-3 的[对象展开运算符](https://github.com/sebmarkbage/ecmascript-rest-spread)我们可以这样写:
+
+    ``` js
+    state.obj = { ...state.obj, newProp: 123 }
+    ```
+
+### 使用常量替代 Mutation 事件类型
+
+使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
+
+``` js
+// mutation-types.js
+export const SOME_MUTATION = 'SOME_MUTATION'
+```
+
+``` js
+// store.js
+import Vuex from 'vuex'
+import { SOME_MUTATION } from './mutation-types'
+
+const store = new Vuex.Store({
+  state: { ... },
+  mutations: {
+    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
+    [SOME_MUTATION] (state) {
+      // mutate state
+    }
+  }
+})
+```
+
+用不用常量取决于你 —— 在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
+
+### mutation 必须是同步函数
+
+一条重要的原则就是要记住** mutation 必须是同步函数**。为什么?请参考下面的例子:
+
+``` js
+mutations: {
+  someMutation (state) {
+    api.callAsyncMethod(() => {
+      state.count++
+    })
+  }
+}
+```
+
+现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回掉函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用 —— 实质上任何在回调函数中进行的的状态的改变都是不可追踪的。
+
+### 在组件中提交 Mutations
+
+你可以在组件中使用 `this.$store.commit('xxx')` 提交 mutation,或者使用 `mapMutations` 辅助函数将组件中的 methods 映射为 `store.commit` 调用(需要在根节点注入 `store`)。
+
+``` js
+import { mapMutations } from 'vuex'
+
+export default {
+  // ...
+  methods: {
+    ...mapMutations([
+      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
+    ]),
+    ...mapMutations({
+      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
+    })
+  }
+}
+```
+
+### 下一步:Actions
+
+在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,**mutation 都是同步事务**:
+
+``` js
+store.commit('increment')
+// 任何由 "increment" 导致的状态变更都应该在此刻完成。
+```
+
+为了处理异步操作,让我们来看一看 [Actions](actions.md)。

+ 120 - 0
docs/zh-cn/plugins.md

@@ -0,0 +1,120 @@
+# 插件
+
+Vuex 的 store 接受 `plugins` 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 sotre 作为唯一参数:
+
+``` js
+const myPlugin = store => {
+  // 当 store 初始化后调用
+  store.subscribe((mutation, state) => {
+    // 每次 mutation 之后调用
+    // mutation 的格式为 { type, payload }
+  })
+}
+```
+
+然后像这样使用:
+
+``` js
+const store = new Vuex.Store({
+  // ...
+  plugins: [myPlugin]
+})
+```
+
+### 在插件内提交 Mutation
+
+在插件中不允许直接修改状态——类似于组件,只能通过提交 mutation 来触发变化。
+
+通过提交 mutation,插件可以用来同步数据源到 store。例如,同步 websocket 数据源到 store(下面是个大概例子,实际上 `createPlugin` 方法可以有更多选项来完成复杂任务):
+
+``` js
+export default function createWebSocketPlugin (socket) {
+  return store => {
+    socket.on('data', data => {
+      store.commit('receiveData', data)
+    })
+    store.subscribe(mutation => {
+      if (mutation.type === 'UPDATE_DATA') {
+        socket.emit('update', mutation.payload)
+      }
+    })
+  }
+}
+```
+
+``` js
+const plugin = createWebSocketPlugin(socket)
+
+const store = new Vuex.Store({
+  state,
+  mutations,
+  plugins: [plugin]
+})
+```
+
+### 生成 State 快照
+
+有时候插件需要获得状态的『快照』,比较改变的前后状态。想要实现这项功能,你需要对状态对象进行深拷贝:
+
+``` js
+const myPluginWithSnapshot = store => {
+  let prevState = _.cloneDeep(store.state)
+  store.subscribe((mutation, state) => {
+    let nextState = _.cloneDeep(state)
+
+    // 比较 prevState 和 nextState...
+
+    // 保存状态,用于下一次 mutation
+    prevState = nextState
+  })
+}
+```
+
+**生成状态快照的插件应该只在开发阶段使用**,使用 Webpack 或 Browserify,让构建工具帮我们处理:
+
+``` js
+const store = new Vuex.Store({
+  // ...
+  plugins: process.env.NODE_ENV !== 'production'
+    ? [myPluginWithSnapshot]
+    : []
+})
+```
+
+上面插件会默认启用。在发布阶段,你需要使用 Webpack 的 [DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin) 或者是 Browserify 的 [envify](https://github.com/hughsk/envify) 使 `process.env.NODE_ENV !== 'production'` 为 `false`。
+
+### 内置 Logger 插件
+
+> 如果正在使用 [vue-devtools](https://github.com/vuejs/vue-devtools),你可能不需要此插件。
+
+Vuex 自带一个日志插件用于一般的调试:
+
+``` js
+import createLogger from 'vuex/dist/logger'
+
+const store = new Vuex.Store({
+  plugins: [createLogger()]
+})
+```
+
+`createLogger` 函数有几个配置项:
+
+``` js
+const logger = createLogger({
+  collapsed: false, // 自动展开记录的 mutation
+  transformer (state) {
+    // 在开始记录之前转换状态
+    // 例如,只返回指定的子树
+    return state.subTree
+  },
+  mutationTransformer (mutation) {
+    // mutation 按照 { type, payload } 格式记录
+    // 我们可以按任意方式格式化
+    return mutation.type
+  }
+})
+```
+
+日志插件还可以直接通过 `<script>` 标签引入,它会提供全局方法 `createVuexLogger`。
+
+要注意,logger 插件会生成状态快照,所以仅在开发环境使用。

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

@@ -0,0 +1,108 @@
+# State
+
+### 单一状态树
+
+Vuex 使用 **单一状态树** —— 是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源([SSOT](https://en.wikipedia.org/wiki/Single_source_of_truth))』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
+
+单状态树和模块化并不冲突 —— 在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。
+
+### 在 Vue 组件中获得 Vuex 状态
+
+那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在[计算属性](http://vuejs.org.cn/guide/computed.html)中返回某个状态:
+
+``` js
+// 创建一个 Counter 组件
+const Counter = {
+  template: `<div>{{ count }}</div>`,
+  computed: {
+    count () {
+      return store.state.count
+    }
+  }
+}
+```
+
+每当 `store.state.count` 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
+
+然而,这种模式导致组件依赖的全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。
+
+Vuex 通过 `store` 选项,提供了一种机制将状态从根组件『注入』到每一个子组件中(需调用 `Vue.use(Vuex)`):
+
+``` js
+const app = new Vue({
+  el: '#app',
+  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
+  store,
+  components: { Counter },
+  template: `
+    <div class="app">
+      <counter></counter>
+    </div>
+  `
+})
+```
+
+通过在根实例中注册 `store` 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 `this.$store` 访问到。让我们更新下 `Counter` 的实现:
+
+``` js
+const Counter = {
+  template: `<div>{{ count }}</div>`,
+  computed: {
+    count () {
+      return this.$store.state.count
+    }
+  }
+}
+```
+
+### `mapState` 辅助函数
+
+当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 `mapState` 辅助函数帮助我们生成计算属性,让你少按几次键:
+
+``` js
+// 在单独构建的版本中辅助函数为 Vuex.mapState
+import { mapState } from 'vuex'
+
+export default {
+  // ...
+  computed: mapState({
+    // 箭头函数可使代码更简练
+    count: state => state.count,
+
+    // 传字符串参数 'count' 等同于 `state => state.count`
+    countAlias: 'count',
+
+    // 为了能够使用 `this` 获取本地状态,必须使用常规函数
+    countPlusLocalState (state) {
+      return state.count + this.localCount
+    }
+  })
+}
+```
+
+当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 `mapState` 传一个字符串数组。
+
+``` js
+computed: mapState([
+  // 映射 this.count 为 store.state.count
+  'count'
+])
+```
+
+### 对象展开运算符
+
+`mapState` 函数返回的是一个对象。我们如何将它与本地计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 `computed` 属性。但是自从有了[对象展开运算符](https://github.com/sebmarkbage/ecmascript-rest-spread)(现处于 ECMASCript 提案 stage-3 阶段),我们可以极大地简化写法:
+
+``` js
+computed: {
+  localComputed () { /* ... */ },
+  // 使用对象展开运算符将此对象混入到外部对象中
+  ...mapState({
+    // ...
+  })
+}
+```
+
+### 组件仍然保有本地状态
+
+使用 Vuex 并不意味着你需要将**所有的**状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的本地状态。你应该根据你的应用开发需要进行权衡和确定。

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

@@ -0,0 +1,25 @@
+# 严格模式
+
+开启严格模式,仅需在创建 store 的时候传入 `strict: true`:
+
+``` js
+const store = new Vuex.Store({
+  // ...
+  strict: true
+})
+```
+
+在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
+
+### 开发环境与发布环境
+
+**不要在发布环境下启用严格模式!**严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
+
+类似于插件,我们可以让构建工具来处理这种情况:
+
+``` js
+const store = new Vuex.Store({
+  // ...
+  strict: process.env.NODE_ENV !== 'production'
+})
+```

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

@@ -0,0 +1,33 @@
+# 项目结构
+
+Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
+
+1. 应用层级的状态应该集中到单个 store 对象中。
+
+2. 提交 **mutation** 是更改状态的唯一方法,并且这个过程是同步的。
+
+3. 异步逻辑都应该封装到 **action**里面。
+
+只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation、和 getters 分割到单独的文件。
+
+对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
+
+
+``` bash
+├── index.html
+├── main.js
+├── api
+│   └── ... # 抽取出API请求
+├── components
+│   ├── App.vue
+│   └── ...
+└── store
+    ├── index.js          # 我们组装模块并导出 store 的地方
+    ├── actions.js        # 根级别的 action
+    ├── mutations.js      # 根级别的 mutation
+    └── modules
+        ├── cart.js       # 购物车模块
+        └── products.js   # 产品模块
+```
+
+请参考[购物车示例](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart)

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

@@ -0,0 +1,215 @@
+# 测试
+
+我们主要想针对 Vuex 中的 mutaions 和 actions 进行单元测试。
+
+### 测试 Mutations
+
+Mutations 很容易被测试,因为它们仅仅是一些完全依赖参数的函数。这里有一个小技巧,如果你在 `store.js` 文件中定义了 mutations,并且使用 ES2015 模块功能默认输出了 Vuex.Store 的实例,那么你仍然可以给 mutation 取个变量名然后把它输出去:
+
+``` js
+const state = { ... }
+
+// mutations 作为命名输出对象
+export const mutations = { ... }
+
+export default new Vuex.Store({
+  state,
+  mutations
+})
+```
+
+下面是用 Mocha + Chai 测试一个 mutation 的例子(实际上你可以用任何你喜欢的测试框架):
+
+``` js
+// mutations.js
+export const mutations = {
+  increment: state => state.count++
+}
+```
+
+``` js
+// mutations.spec.js
+import { expect } from 'chai'
+import { mutations } from './store'
+
+// 解构 mutations
+const { increment } = mutations
+
+describe('mutations', () => {
+  it('INCREMENT', () => {
+    // 模拟状态
+    const state = { count: 0 }
+    // 应用 mutation
+    increment(state)
+    // 断言结果
+    expect(state.count).to.equal(1)
+  })
+})
+```
+
+### 测试 Actions
+
+Actions 应对起来略微棘手,因为它们可能需要调用外部的 API。当测试 actions 的时候,我们需要增加一个 mocking 服务层 —— 例如,我们可以把 API 调用抽象成服务,然后在测试文件中用 mock 服务回应 API 调用。为了便于解决 mock 依赖,可以用 Webpack 和  [inject-loader](https://github.com/plasticine/inject-loader) 打包测试文件。
+
+下面是一个测试异步 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
+
+// 使用 require 语法处理内联 loaders。
+// inject-loader 返回一个允许我们注入 mock 依赖的模块工厂
+import { expect } from 'chai'
+const actionsInjector = require('inject!./actions')
+
+// 使用 mocks 创建模块
+const actions = actionsInjector({
+  '../api/shop': {
+    getProducts (cb) {
+      setTimeout(() => {
+        cb([ /* mocked response */ ])
+      }, 100)
+    }
+  }
+})
+
+// 用指定的 mutaions 测试 action 的辅助函数
+const testAction = (action, args, state, expectedMutations, done) => {
+  let count = 0
+
+  // 模拟提交
+  const commit = (type, payload) => {
+    const mutation = expectedMutations[count]
+    expect(mutation.type).to.equal(type)
+    if (payload) {
+      expect(mutation.payload).to.deep.equal(payload)
+    }
+    count++
+    if (count >= expectedMutations.length) {
+      done()
+    }
+  }
+
+  // 用模拟的 store 和参数调用 action
+  action({ commit, state }, ...args)
+
+  // 检查是否没有 mutation 被 dispatch
+  if (expectedMutations.length === 0) {
+    expect(count).to.equal(0)
+    done()
+  }
+}
+
+describe('actions', () => {
+  it('getAllProducts', done => {
+    testAction(actions.getAllProducts, [], {}, [
+      { type: 'REQUEST_PRODUCTS' },
+      { type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } }
+    ], done)
+  })
+})
+```
+
+### 测试 Getters
+
+如果你的 getter 包含很复杂的计算过程,很有必要测试它们。Getter 的测试与 mutation 一样直截了当。
+
+测试一个 getter 的示例:
+
+``` js
+// getters.js
+export const getters = {
+  filteredProducts (state, { filterCategory }) {
+    return state.products.filter(product => {
+      return product.category === filterCategory
+    })
+  }
+}
+```
+
+``` js
+// getters.spec.js
+import { expect } from 'chai'
+import { getters } from './getters'
+
+describe('getters', () => {
+  it('filteredProducts', () => {
+    // 模拟状态
+    const state = {
+      products: [
+        { id: 1, title: 'Apple', category: 'fruit' },
+        { id: 2, title: 'Orange', category: 'fruit' },
+        { id: 3, title: 'Carrot', category: 'vegetable' }
+      ]
+    }
+    // 模拟 getter
+    const filterCategory = 'fruit'
+
+    // 获取 getter 的结果
+    const result = getters.filteredProducts(state, { filterCategory })
+
+    // 断言结果
+    expect(result).to.deep.equal([
+      { id: 1, title: 'Apple', category: 'fruit' },
+      { id: 2, title: 'Orange', category: 'fruit' }
+    ])
+  })
+})
+```
+
+### 执行测试
+
+如果你的 mutations 和 actions 编写正确,经过合理地 mocking 处理之后这些测试应该不依赖任何浏览器 API,因此你可以直接用 Webpack 打包这些测试文件然后在 Node 中执行。换种方式,你也可以用 `mocha-loader` 或 `Karma` + `karma-webpack`在真实浏览器环境中进行测试。
+
+#### 在 Node 中执行测试
+
+创建以下 webpack 配置(配置好 [`.babelrc`](https://babeljs.io/docs/usage/babelrc/)):
+
+``` js
+// webpack.config.js
+module.exports = {
+  entry: './test.js',
+  output: {
+    path: __dirname,
+    filename: 'test-bundle.js'
+  },
+  module: {
+    loaders: [
+      {
+        test: /\.js$/,
+        loader: 'babel',
+        exclude: /node_modules/
+      }
+    ]
+  }
+}
+```
+
+然后:
+
+``` bash
+webpack
+mocha test-bundle.js
+```
+
+#### 在浏览器中测试
+
+1. 安装 `mocha-loader`
+2. 把上述 webpack 配置中的 `entry` 改成 `'mocha!babel!./test.js'`
+3. 用以上配置启动 `webpack-dev-server`
+4. 访问 `localhost:8080/webpack-dev-server/test-bundle`.
+
+#### 使用 Karma + karma-webpack 在浏览器中执行测试
+
+详见 [vue-loader documentation](http://vuejs.github.io/vue-loader/workflow/testing.html)。