Skip to content

Commit 86f1c5a

Browse files
committed
[docs] actions
1 parent 1e592a8 commit 86f1c5a

File tree

2 files changed

+76
-93
lines changed

2 files changed

+76
-93
lines changed

docs/en/actions.md

Lines changed: 74 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,117 @@
11
# Actions
22

3-
Actions are functions that dispatch mutations. Actions can be asynchronous and a single action can dispatch multiple mutations.
4-
5-
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.
6-
7-
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.
8-
93
> Vuex actions are in fact "action creators" in vanilla flux definitions, but I find that term more confusing than useful.
104
11-
### Simple Actions
12-
13-
It is common that an action simply triggers a single mutation. Vuex provides a shorthand for defining such actions:
5+
Actions are just functions that dispatch mutations. By convention, Vuex actions always expect a store instance as its first argument, followed by optional additional arguments:
146

157
``` js
16-
const store = new Vuex.Store({
17-
state: {
18-
count: 1
19-
},
20-
mutations: {
21-
INCREMENT (state, x) {
22-
state.count += x
23-
}
24-
},
25-
actions: {
26-
// shorthand
27-
// just provide the mutation name.
28-
increment: 'INCREMENT'
29-
}
30-
})
8+
// the simplest action
9+
function increment (store) {
10+
store.dispatch('INCREMENT')
11+
}
12+
13+
// a action with additional arguments
14+
// with ES2015 argument destructuring
15+
function incrementBy ({ dispatch }, amount) {
16+
dispatch('INCREMENT', amount)
17+
}
3118
```
3219

33-
Now when we call the action:
20+
This may look dumb at first sight: why don't we just dispatch mutations directly? Well, remember that **mutations must be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action:
3421

3522
``` js
36-
store.actions.increment(1)
23+
function incrementAsync ({ dispatch }) {
24+
setTimeout(() => {
25+
dispatch('INCREMENT')
26+
}, 1000)
27+
}
3728
```
3829

39-
It simply calls the following for us:
30+
A more practical example would be an action to checkout a shopping cart, which involves **calling an async API** and **dispatching multiple mutations**:
4031

4132
``` js
42-
store.dispatch('INCREMENT', 1)
33+
function checkout ({ dispatch, state }, products) {
34+
// save the current in cart items
35+
const savedCartItems = [...state.cart.added]
36+
// send out checkout request, and optimistically
37+
// clear the cart
38+
dispatch(types.CHECKOUT_REQUEST)
39+
// the shop API accepts a success callback and a failure callback
40+
shop.buyProducts(
41+
products,
42+
// handle success
43+
() => dispatch(types.CHECKOUT_SUCCESS),
44+
// handle failure
45+
() => dispatch(types.CHECKOUT_FAILURE, savedCartItems)
46+
)
47+
}
4348
```
4449

45-
Note any arguments passed to the action is also passed along to the mutation handler.
50+
Note that instead of expecting returns values or passing callbacks to actions, the result of calling the async API is handled by dispatching mutations as well. The rule of thumb is that **the only side effects produced by calling actions should be dispatched mutations**.
4651

47-
### Normal Actions
52+
### Calling Actions In Components
4853

49-
For actions that involve logic depending on current state, or that need async operations, we define them as functions. Action functions always get the store calling it as the first argument:
54+
You may have noticed that action functions are not directly callable without reference to a store instance. Technically, we can invoke an action by calling `action(this.$store)` inside a method, but it's better if we can directly expose "bound" versions of actions as the component's methods so that we can easily refer to them inside templates. We can do that using the `vuex.actions` option:
5055

5156
``` js
52-
const vuex = new Vuex.Store({
53-
state: {
54-
count: 1
55-
},
56-
mutations: {
57-
INCREMENT (state, x) {
58-
state += x
59-
}
60-
},
61-
actions: {
62-
incrementIfOdd: (store, x) => {
63-
if ((store.state.count + 1) % 2 === 0) {
64-
store.dispatch('INCREMENT', x)
65-
}
57+
// inside a component
58+
import { incrementBy } from './actions'
59+
60+
const vm = new Vue({
61+
vuex: {
62+
state: { ... }, // state getters
63+
actions: {
64+
incrementBy // ES6 object literal shorthand, bind using the same name
6665
}
6766
}
6867
})
6968
```
7069

71-
It is common to use ES6 argument destructuring to make the function body less verbose (here the `dispatch` function is pre-bound to the store instance so we don't have to call it as a method):
70+
What the above code does is binding the raw `incrementBy` action to the component's store instance, and expose it on the component as an instance method, `vm.incrementBy`. Any arguments passed to `vm.incrementBy` will be passed to the raw action function after the first argument which is the store, so calling:
7271

7372
``` js
74-
// ...
75-
actions: {
76-
incrementIfOdd: ({ dispatch, state }, x) => {
77-
if ((state.count + 1) % 2 === 0) {
78-
dispatch('INCREMENT', x)
79-
}
80-
}
81-
}
73+
vm.incrementBy(1)
8274
```
8375

84-
The string shorthand is essentially syntax sugar for the following:
76+
is equivalent to:
8577

8678
``` js
87-
actions: {
88-
increment: 'INCREMENT'
89-
}
90-
// ... equivalent to:
91-
actions: {
92-
increment: ({ dispatch }, ...payload) => {
93-
dispatch('INCREMENT', ...payload)
94-
}
95-
}
79+
incrementBy(vm.$store, 1)
9680
```
9781

98-
### Async Actions
82+
But the benefit is that we can bind to it more easily inside the component's template:
9983

100-
We can use the same syntax for defining async actions:
84+
``` html
85+
<button v-on:click="incrementBy(1)">increment by one</button>
86+
```
87+
88+
You can obviously use a different method name when binding actions:
10189

10290
``` js
103-
// ...
104-
actions: {
105-
incrementAsync: ({ dispatch }, x) => {
106-
setTimeout(() => {
107-
dispatch('INCREMENT', x)
108-
}, 1000)
91+
// inside a component
92+
import { incrementBy } from './actions'
93+
94+
const vm = new Vue({
95+
vuex: {
96+
state: { ... },
97+
actions: {
98+
plus: incrementBy // bind using a different name
99+
}
109100
}
110-
}
101+
})
111102
```
112103

113-
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:
104+
Now the action will be bound as `vm.plus` instead of `vm.incrementBy`.
105+
106+
Finally, if you simply want to bind all the actions:
114107

115108
``` js
116-
// ...
117-
actions: {
118-
checkout: ({ dispatch, state }, products) => {
119-
// save the current in cart items
120-
const savedCartItems = [...state.cart.added]
121-
// send out checkout request, and optimistically
122-
// clear the cart
123-
dispatch(types.CHECKOUT_REQUEST)
124-
// the shop API accepts a success callback and a failure callback
125-
shop.buyProducts(
126-
products,
127-
// handle success
128-
() => dispatch(types.CHECKOUT_SUCCESS),
129-
// handle failure
130-
() => dispatch(types.CHECKOUT_FAILURE, savedCartItems)
131-
)
109+
import * as actions from './actions'
110+
111+
const vm = new Vue({
112+
vuex: {
113+
state: { ... },
114+
actions // bind all actions
132115
}
133-
}
116+
})
134117
```
135-
136-
Again, all the component needs to do to perform the entire checkout is just calling `store.actions.checkout(products)`.

docs/en/state.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ However, this pattern causes the component to rely on the global store singleton
4646
})
4747
```
4848

49+
By providing the `store` option to the root instance, the store will be injected into all child components of the root and will be available on them as `this.$store`. However it's quite rare that we will need to actually reference it.
50+
4951
2. Inside child components, retrieve state using the `vuex.state` option:
5052

5153
``` js

0 commit comments

Comments
 (0)