Randy 9 лет назад
Родитель
Сommit
5e5051438d

+ 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
+
+- [What is Vuex?](intro.md)
+- [Core Concepts](concepts.md)
+  - [State](state.md)
+  - [Mutations](mutations.md)
+  - [Actions](actions.md)
+- [Data Flow](data-flow.md)
+- [Application Structure](structure.md)
+- [Middlewares](middlewares.md)
+- [Strict Mode](strict.md)
+- [Form Handling](forms.md)
+- [Testing](testing.md)
+- [Hot Reloading](hot-reload.md)
+- [API Reference](api.md)

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

@@ -0,0 +1,138 @@
+# Actions
+
+Actions are functions that dispatch mutations. Actions can be asynchronous and a single action can dispatch multiple mutations.
+
+An action expresses the intention for something to happen, and abstracts the details away from the component calling it. When a component wants to do something, it just calls an action - there's no need to worry about a callback or a return value, because actions result in state changes, and state changes will trigger the component's DOM to update - the component is completely decoupled from how that action is actually performed.
+
+Therefore, we usually perform API calls to data endpoints inside actions, and hide the asynchronous details from both the Components calling the actions, and the mutations triggered by the actions.
+
+> Vuex actions are in fact "action creators" in vanilla flux definitions, but I find that term more confusing than useful.
+
+### Simple Actions
+
+It is common that an action simply triggers a single mutation. Vuex provides a shorthand for defining such actions:
+
+``` js
+const vuex = new Vuex({
+  state: {
+    count: 1
+  },
+  mutations: {
+    INCREMENT (state, x) {
+      state += x
+    }
+  },
+  actions: {
+    // shorthand
+    // just provide the mutation name.
+    increment: 'INCREMENT'
+  }
+})
+```
+
+Now when we call the action:
+
+``` js
+vuex.actions.increment(1)
+```
+
+It simply calls the following for us:
+
+``` js
+vuex.dispatch('INCREMENT', 1)
+```
+
+Note any arguments passed to the action is also passed along to the mutation handler.
+
+### Thunk Actions
+
+How about actions that involve logic depending on current state, or that need async operations? We can define these actions as **thunks** - essentially functions that return another function:
+
+``` 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)
+        }
+      }
+    }
+  }
+})
+```
+
+Here, the outer function receives the arguments passed when calling the action. Then, it returns a function that gets 2 arguments: first is the `dispatch` function, and the second being the `state`. We are using ES5 syntax here to make things easier to understand. With ES2015 arrow functions we can "prettify" the above to the following:
+
+``` js
+// ...
+actions: {
+  incrementIfOdd: x => (dispatch, state) => {
+    if ((state.count + 1) % 2 === 0) {
+      dispatch('INCREMENT', x)
+    }
+  }
+}
+```
+
+The string shorthand is essentially syntax sugar for the following:
+
+``` js
+actions: {
+  increment: 'INCREMENT'
+}
+// ... equivalent to:
+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!
+
+### Async Actions
+
+We can use the same thunk syntax for defining async actions:
+
+``` js
+// ...
+actions: {
+  incrementAsync: x => dispatch => {
+    setTimeout(() => {
+      dispatch('INCREMENT', x)
+    }, 1000)
+  }
+}
+```
+
+A more practical example is when checking out a shopping cart - we may need to trigger multiple mutations: one that signifies the checkout has started, one for success, and one for failure:
+
+``` 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)
+    )
+  }
+}
+```
+
+Again, all the component needs to do to perform the entire checkout is just calling `vuex.actions.checkout(products)`.

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

@@ -0,0 +1,93 @@
+# API Reference
+
+### Constructor
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({ ...options })
+```
+
+### Constructor Options
+
+- **state**
+  
+  - type: `Object`
+
+    The root state object for the Vuex instance.
+
+    [Details](state.md)
+
+- **mutations**
+
+  - type: `Object | Array<Object>`
+
+    An object where each entry's key is the mutation name and the value is a mutation handler function. The handler function always receives `state` as the first argument, and receives all arguments passed to the dispatch call following that.
+
+    If passing in an Array of Objects, these objects will be automatically merged together into one final object.
+
+    [Details](mutations.md)
+
+- **actions**
+
+  - type: `Object | Array<Object>`
+
+    An object where each entry's key is the action name and the value is either
+
+    1. A mutation name string; or
+    2. A thunk action creator function.
+
+    Vuex will process these entries and create the actual callable action functions and expose them on the `actions` property of the instance.
+
+    If passing in an Array of Objects, these objects will be automatically merged together into one final object.
+
+    [Details](actions.md)
+
+- **middlewares**
+
+  - type: `Array<Object>`
+
+    An array of middleware objects that are in the shape of:
+
+    ``` js
+    {
+      snapshot: Boolean, // default: false
+      onInit: Function,
+      onMutation: Function
+    }
+    ```
+
+    All fields are optional. [Details](middlewares.md)
+
+- **strict**
+
+  - type: `Boolean`
+  - default: `false`
+
+    Force the Vuex instance into strict mode. In strict mode any mutations to Vuex state outside of mutation handlers will throw en Error.
+
+    [Details](strict.md)
+
+### Instance Properties
+
+- **state**
+
+  - type: `Object`
+
+    The root state. Read only.
+
+- **actions**
+
+  - type: `Object`
+
+    The callable action functions.
+
+### Instance Methods
+
+- **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)**
+
+  Hot swap new actions and mutations. [Details](hot-reload.md)

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

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

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

@@ -0,0 +1,34 @@
+# Core Concepts
+
+Similar to Vue itself, Vuex exposes a single `Vuex` constructor. You can use it to create **Vuex instances**. In most cases, you only need one Vuex instance for an app. You can think of a Vuex instance as an "enhanced store" that holds your app state.
+
+Each Vuex instance consists of three types of "ingredients":
+
+- **State**: A plain object representing the application state.
+
+- **Mutations**: Functions that mutates the state. Mutations **must be synchronous**.
+
+- **Actions**: Functions that dispatch mutations. An action can contain asynchronous operations and can dispatch multiple mutations.
+
+Why do we differentiate between *mutations* and *actions*, rather then just simple functions that manipulate the state however we want? The reason is because we want to **separate mutation and asynchronicity**. A lot of application complexity roots from the combination of the two. When separated, they both become easier to reason about and write tests for.
+
+> If you are familiar with Flux, note there's a term/concept difference here: Vuex mutations are the equivalent of Flux **actions**, while Vuex actions are equivalent to Flux **action creators**.
+
+### Creating a Vuex Instance
+
+> **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! The doc also assumes you are already familiar with the concepts discussed in [Building Large-Scale Apps with Vue.js](http://vuejs.org/guide/application.html).
+
+Creating a Vuex instance is pretty straightforward - just put the aforementioned ingredients together:
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({
+  state: { ... },
+  actions: { ... },
+  mutations: { ... }
+})
+```
+
+Once created, you can access the state via `vuex.state`, and the actions via `vuex.actions`. You cannot directly access the mutation functions - they can only be triggered by actions or calling `vuex.dispatch()`. We will discuss each concept in more details next.
+

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

@@ -0,0 +1,92 @@
+# Date Flow
+
+Let's build a simple counter app with Vuex to get a better understanding of the data flow inside Vuex apps. Note this is a trivial example solely for the purpose of explaining the concepts - in practice you don't need Vuex for such simple tasks.
+
+### Setup
+
+``` js
+// vuex.js
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+```
+
+### Define App State
+
+``` js
+const state = {
+  count: 0
+}
+```
+
+### Define Possible State Mutations
+
+``` js
+const mutations = {
+  INCREMENT (state) {
+    state.count++
+  },
+  DECREMENT (state) {
+    state.count--
+  }
+}
+```
+
+### Define Callable Actions
+
+``` js
+const actions = {
+  increment: 'INCREMENT',
+  decrement: 'DECREMENT'
+}
+```
+
+### Create a Vuex Instance
+
+``` js
+export default new Vuex({
+  state,
+  mutations,
+  actions
+})
+```
+
+### Use It in a Vue Component
+
+**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: {
+    // bind to state using computed properties
+    count () {
+      return vuex.state.count
+    }
+  },
+  methods: {
+    increment: vuex.actions.increment,
+    decrement: vuex.actions.decrement
+  }
+}
+```
+
+Here you will notice the component itself is extremely simple: it simply displays some state from the Vuex instance (not even owning its own data), and calls some vuex actions on user input events.
+
+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 @@
+# Form Handling
+
+When using Vuex in strict mode, it could be a bit tricky to use `v-model` on a piece of state that belongs to Vuex:
+
+``` html
+<input v-model="obj.message">
+```
+
+Assuming `obj` is a computed property that returns an Object from Vuex state, the `v-model` here will attempt to directly mutate `obj.message` when the user types in the input. In strict mode, this will result in en error because the mutation is not performed inside an explicit Vuex mutation handler.
+
+The "Vuex way" to deal with it is binding the `<input>`'s value and call an action on the `input` or `change` event:
+
+``` html
+<input :value="obj.message" @input="updateMessage">
+```
+``` js
+// ...
+methods: {
+  updateMessage: function (e) {
+    vuex.actions.updateMessage(e.target.value)
+  }
+}
+```
+
+Assuming the `updateMessage` action simply dispatches `'UPDATE_MESSAGE'`, here's the mutation handler:
+
+``` js
+// ...
+mutations: {
+  UPDATE_MESSAGE (state, message) {
+    state.obj.message = message
+  }
+}
+```
+
+Admittedly, this is quite a bit more verbose than a simple `v-model`, but such is the cost of making state changes explicit and track-able. At the same time, do note that Vuex doesn't demand putting all your state inside a Vuex instance - if you do not wish to track the mutations for form interactions at all, you can simply keep the form state outside of Vuex as component local state, which allows you to freely leverage `v-model`.

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

@@ -0,0 +1,29 @@
+# Hot Reloading
+
+Vuex supports hot-reloading actions and mutations during development, using Webpack's [Hot Module Replacement API](https://webpack.github.io/docs/hot-module-replacement.html). You can also use it in Browserify with the [browserify-hmr](https://github.com/AgentME/browserify-hmr/) plugin.
+
+It's as simple as calling `vuex.hotUpdate()` with the new actions and mutations:
+
+``` js
+// ...
+const vuex = new Vuex({
+  state,
+  actions,
+  mutations
+})
+
+if (module.hot) {
+  // accept actions and mutations as hot modules
+  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 应用的应用状态管理结构。它受启发于 [Flux](https://facebook.github.io/flux/) 和 [Redux](https://github.com/rackt/redux),但 Vuex 的设计更加符合 Vue.js 的响应系统(reactivity system)。
+
+## 我为什么需要它?
+
+当你的应用还很简单的时候,你可能并不需要 Vuex,也不建议在过早地去用它。相反,如果你正在构建一个规模越来越大的 SPA 时,你会开始考虑如何管理好 Vue 组件之外的东西。这就是 Vuex 要做的事。
+
+我们在用 Vue.js 的时候,通常会把状态储存在组件的内部。也就是说,每一个组件都拥有自己单独的状态。然而有时候我们需要把一部分的状态共享给其它组件,通常我们都是使用第三方的事件系统去达到这个目的。这个模式的问题在于,事件流会在逐渐增长的组件树中变得复杂,并且在出现问题的时候很难去发现问题的所在。
+
+为了更好的解决在大型应用中共享状态的问题,我们需要把组件状态(component local state)和应用级状态(application level state)区分开来。应用级的状态不属于任何特定的组件,但每一个组件仍然可以观察到状态变化从而自动更新 DOM。通过把状态管理放在同一个地方,我们就不需要在各个地方去处理各种事件,因为任何牵扯到多个组件的东西,都会放在这个地方。这样做也能使我们可以记录和检查 state 的改变,甚至可以做些像 time-travel 这样的调试。
+
+即使 Vuex 在状态管理的分离方面有很多的约定,但不会影响你实际代码结构的灵活性。

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

@@ -0,0 +1,81 @@
+# Middlewares
+
+Vuex instances accept the `middlewares` option that exposes hooks for each mutation (Note this is completely unrelated to Redux middlewares). A Vuex middleware is simply an object that implements some hook functions:
+
+``` js
+const myMiddleware = {
+  onInit (state) {
+    // record initial state
+  },
+  onMutation (mutation, state) {
+    // called after every mutation.
+    // The mutation comes in the format of { type, payload }
+  }
+}
+```
+
+And can be used like this:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  middlewares: [myMiddleware]
+})
+```
+
+By default, a middleware receives the actual `state` object. Since middlewares are primarily used for debugging purposes, they are **not allowed to mutate the state**.
+
+Sometimes a middleware may want to receive "snapshots" of the state, and also compare the post-mutation state with pre-mutation state. Such middlewares must declare the `snapshot: true` option:
+
+``` js
+const myMiddlewareWithSnapshot = {
+  snapshot: true,
+  onMutation (mutation, nextState, prevState) {
+    // nextState and prevState are deep-cloned snapshots
+    // of the state before and after the mutation.
+  }
+}
+```
+
+**Middlewares that take state snapshots should be used only during development.** When using Webpack or Browserify, we can let our build tools handle that for us:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  middlewares: process.env.NODE_ENV !== 'production'
+    ? [myMiddlewareWithSnapshot]
+    : []
+})
+```
+
+The middleware will be used by default. For production, use the build setup described [here](http://vuejs.org/guide/application.html#Deploying_for_Production) to convert the value of `process.env.NODE_ENV !== 'production'` to `false` for the final build.
+
+### Built-in Logger Middleware
+
+Veux comes with a logger middleware for common debugging usage:
+
+``` js
+const vuex = new Vuex({
+  middlewares: [Vuex.createLogger()]
+})
+```
+
+The `createLogger` function takes a few options:
+
+``` js
+const logger = Vuex.createLogger({
+  collapsed: false, // auto-expand logged mutations
+  transformer (state) {
+    // transform the state before logging it.
+    // for example return only a specific sub-tree
+    return state.subTree
+  },
+  mutationTransformer (mutation) {
+    // mutations are logged in the format of { type, payload }
+    // we can format it anyway we want.
+    return mutation.type
+  }
+})
+```
+
+Note the logger middleware takes state snapshots, so use it only during development.

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

@@ -0,0 +1,87 @@
+# Mutations
+
+Vuex mutations are essentially events: each mutation has a **name** and a **handler**. The handler function always gets the entire state tree as the first argument:
+
+``` js
+import Vuex from 'vuex'
+
+const vuex = new Vuex({
+  state: {
+    count: 1
+  },
+  mutations: {
+    INCREMENT (state) {
+      // mutate state
+      state.count++
+    }
+  }
+})
+```
+
+Using all caps for mutation names is just a convention to make it easier to differentiate them from actions.
+
+You cannot directly call a mutation handler. The options here is more like event registration: "When an `INCREMENT` event is dispatched, call this handler." To invoke a mutation handler, you need to dispatch a mutation event:
+
+``` js
+vuex.dispatch('INCREMENT')
+```
+
+### Dispatch with Arguments
+
+It is also possible to pass along arguments:
+
+``` js
+// ...
+mutations: {
+  INCREMENT (state, n) {
+    state.count += n
+  }
+}
+```
+``` js
+vuex.dispatch('INCREMENT', 10)
+```
+
+Here `10` will be passed to the mutation handler as the second argument following `state`. Same for any additional arguments. These arguments are called the **payload** for the given mutation.
+
+### Mutations Follow Vue's Reactivity Rules
+
+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).
+
+### Using Constants for Mutation Names
+
+It is also common to use constants for mutation names - they allow the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application:
+
+``` 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
+    }
+  }
+})
+```
+
+Whether to use constants is largely a preference - it can be helpful in large projects with many developers, but it's totally optional if you don't like them.
+
+### On to Actions
+
+Manually calling `vuex.dispatch` is possible, but in practice, we will rarely do this in our component code. Most of the time we will be calling [actions](actions.md), which can encapsulate more complex logic such as async data fetching.
+
+Also, one important rule to remember: all mutation handlers must be **synchronous**. Any async operations belong in 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
+
+### Single State Tree
+
+Vuex uses a **single state tree** - that is, this single object contains all your application level state and serves as the "single source of truth". This makes it straightforward to locate a specific piece of state, and allows us to easily take snapshots of the current app state for debugging purposes.
+
+The single state tree does not conflict with modularity - in later chapters we will discuss how to split your state managing logic into sub modules.
+
+### Getting Vuex State into Vue Components
+
+Similar to `data` objects passed to Vue instances, the `state` object, once passed into a Vuex instance, becomes reactive powered by [Vue's reactivity system](http://vuejs.org/guide/reactivity.html). This means binding Vuex state to Vue components is as simple as returning it from within a computed property:
+
+``` js
+// inside a Vue component module
+
+// import a vuex instance
+import vuex from './vuex'
+
+export default {
+  computed: {
+    message () {
+      return vuex.state.message
+    }
+  }
+}
+```
+
+There's no need to worry about setting up and tearing down listeners, or "connecting" the component to a store. The only thing to remember is that you should **always reference state via `vuex.state.xxx` inside your computed properties**. Do not cache the reference to a piece of state outside computed properties.
+
+> 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.
+
+Why don't we just use `data` to bind to vuex state? Consider the following example:
+
+``` js
+export default {
+  data () {
+    return {
+      message: vuex.state.message
+    }
+  }
+}
+```
+
+Because the `data` function does not track any reactive dependencies, we are only getting a static reference to `vuex.state.message`. When the Vuex state is mutated later, the component has no idea that something has changed. In comparison, computed properties track all reactive dependencies when they are evaluated, so they reactively re-evaluate when the related vuex state mutates.
+
+### Components Are Not Allowed to Directly Mutate State
+
+Using read-only computed properties has another benefit in that it helps emphasizing the rule that **components should never directly mutate Vuex state**. Because we want every state mutation to be explicit and trackable, all vuex state mutations must be conducted inside vuex mutation handlers.
+
+To help enforce this rule, when in [Debug Mode](debug.md), if Vuex state is mutated outside of mutation handlers, Vuex will throw an error.
+
+With this rule in place, our Vue components now hold a lot less responsibility: they connect to Vuex state via read-only computed properties, and the only way for them to affect Vuex state is by calling **actions**, which in turn trigger **mutations**. They can still possess and operate on their local state if necessary, but we no longer put any data-fetching or global-state-mutating logic inside individual components.

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

@@ -0,0 +1,25 @@
+# Strict Mode
+
+To enable strict mode, simply pass in `strict: true` when creating the Vuex instance:
+
+``` 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.
+
+### Development vs. Production
+
+**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.
+
+Similar to middlewares, we can let the build tools handle that:
+
+``` js
+const vuex = new Vuex({
+  // ...
+  strict: process.env.NODE_ENV !== 'production'
+})
+```

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

@@ -0,0 +1,133 @@
+# Application Structure
+
+Vuex doesn't really restrict how you structure your code. Rather, it enforces a set of opinions:
+
+1. Application state lives in a single object.
+2. Only mutation handlers can mutate the state.
+3. Mutations must be synchronous, and the only side effects they produce should be state mutation.
+4. All asynchronous logic such as data fetching should be performed in actions.
+
+The nice thing about Vuex actions and mutations is that **they are just functions with no external dependencies**. As long as you follow these rules, it's up to you how to structure your project. The simplest Vuex instance can even be declared [in a single file](https://github.com/vuejs/vuex/blob/master/examples/counter/vuex.js)! However, this is unlikely to suffice for any serious project, so here are some recommended structures depending on the scale of your app.
+
+### Simple Project
+
+For a simple project, we can simply separate **actions** and **mutations** into respective files:
+
+``` 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
+```
+
+For an actual example, check out the [TodoMVC example](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
+```
+
+A typical module looks like this:
+
+``` 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--
+    }
+  }
+}
+```
+
+And in `vuex/index.js`, we "assemble" multiple modules together to create the Vuex instance:
+
+``` 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).
+
+### Extracting Shared Computed Getters
+
+In large projects, it's possible that multiple components will need the same computed property based on Vuex state. Since computed getters are just functions, you can split them out into a separate file so that they can be shared in any component:
+
+``` 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
+  }
+}
+```
+
+This is very similar to [Getters in NuclearJS](https://optimizely.github.io/nuclear-js/docs/04-getters.html).

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

@@ -0,0 +1,130 @@
+# Testing
+
+Mutations are very straightforward to test, because they are just functions that completely rely on their arguments. Actions can be a bit more tricky because they may call out to external APIs. When testing actions, we usually need to do some level of mocking - for example, we can abstract the API calls into a service and mock that service inside our tests. In order to easily mock dependencies, we can use Webpack and [inject-loader](https://github.com/plasticine/inject-loader) to bundle our test files.
+
+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.
+
+Example testing a mutation using Mocha + Chai (you can use any framework/assertion libraries you like):
+
+``` 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)
+  })
+})
+```
+
+Example testing an async 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)
+  })
+})
+```
+
+### Running in Node
+
+Create the following webpack config:
+
+``` 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']
+  }
+}
+```
+
+Then:
+
+``` bash
+webpack
+mocha test-bundle.js
+```
+
+### Running in Browser
+
+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`.

BIN
docs/zh-cn/vuex.png