|
| 1 | +# Действия |
| 2 | + |
| 3 | +Действия — похожи на мутации, с несколькими отличиями: |
| 4 | + |
| 5 | +- Вместо того чтобы напрямую менять состояние, действия инициируют мутации. |
| 6 | +- Действия могут использоваться для асинхронных операций. |
| 7 | + |
| 8 | +Зарегистрируем простое действие: |
| 9 | + |
| 10 | +``` js |
| 11 | +const store = new Vuex.Store({ |
| 12 | + state: { |
| 13 | + count: 0 |
| 14 | + }, |
| 15 | + mutations: { |
| 16 | + increment (state) { |
| 17 | + state.count++ |
| 18 | + } |
| 19 | + }, |
| 20 | + actions: { |
| 21 | + increment (context) { |
| 22 | + context.commit('increment') |
| 23 | + } |
| 24 | + } |
| 25 | +}) |
| 26 | +``` |
| 27 | + |
| 28 | +Обработчики действий получают объект контекста, содержащий те же методы и свойства, что и сам инстанс хранилища, так что вы можете вызвать `context.commit` для инициирования мутации, или обратиться к геттерам через `context.state` и `context.getters`. Позднее, при рассмотрении [Модулей](modules.md), мы увидем, однако, что этот контекст — не то же самое, что и сам инстанс хранилища. |
| 29 | + |
| 30 | +На практике для упрощения кода часто используется [деструктуризация аргументов](https://github.com/lukehoban/es6features#destructuring) из ES2015 (особенно при необходимости многократного вызова `commit`): |
| 31 | + |
| 32 | +``` js |
| 33 | +actions: { |
| 34 | + increment ({ commit }) { |
| 35 | + commit('increment') |
| 36 | + } |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +### Диспетчеризация действий |
| 41 | + |
| 42 | +Действия запускаются методом `store.dispatch`: |
| 43 | + |
| 44 | +``` js |
| 45 | +store.dispatch('increment') |
| 46 | +``` |
| 47 | + |
| 48 | +На первый взгляд может выглядеть глупо: если мы хотим инкрементировать переменную count, почему бы просто не вызвать `store.commit('increment')` напрямую? Самое время вспомнить, что **мутации обязаны быть синхронными**. Действия же этим ограничением не скованы. Внутри действия можно выполнять **асинхронные** операции: |
| 49 | + |
| 50 | +``` js |
| 51 | +actions: { |
| 52 | + incrementAsync ({ commit }) { |
| 53 | + setTimeout(() => { |
| 54 | + commit('increment') |
| 55 | + }, 1000) |
| 56 | + } |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +Диспетчеризация действий поддерживает такой же объектный синтаксис, как диспетчеризация мутаций: |
| 61 | + |
| 62 | +``` js |
| 63 | +// параметризированный вызов |
| 64 | +store.dispatch('incrementAsync', { |
| 65 | + amount: 10 |
| 66 | +}) |
| 67 | + |
| 68 | +// объектный синтаксис |
| 69 | +store.dispatch({ |
| 70 | + type: 'incrementAsync', |
| 71 | + amount: 10 |
| 72 | +}) |
| 73 | +``` |
| 74 | + |
| 75 | +Более приближённым к реальности примером действий будет формирование заказа на основе состояния корзины покупок. Логика такого действия включает в себя **вызов асинхронного API** и **инициализацию нескольких мутаций**: |
| 76 | + |
| 77 | +``` js |
| 78 | +actions: { |
| 79 | + checkout ({ commit, state }, payload) { |
| 80 | + // сохраним находящиеся на данный момент в корзине товары |
| 81 | + const savedCartItems = [...state.cart.added] |
| 82 | + // инициируем запрос и "оптимистично" очистим корзину |
| 83 | + commit(types.CHECKOUT_REQUEST) |
| 84 | + // предположим, что API магазина позволяет передать колбэки |
| 85 | + // для обработки успеха и неудачи при формировании заказа |
| 86 | + shop.buyProducts( |
| 87 | + products, |
| 88 | + // обработка успешного исхода |
| 89 | + () => commit(types.CHECKOUT_SUCCESS), |
| 90 | + // обработка неудачного исхода |
| 91 | + () => commit(types.CHECKOUT_FAILURE, savedCartItems) |
| 92 | + ) |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +Таким образом удаётся организовать поток асинхронных операций, записывая побочные эффекты действий в виде мутаций состояния. |
| 98 | + |
| 99 | +### Диспетчеризация действий в компонентах |
| 100 | + |
| 101 | +Диспетчеризовать действия в компонентах можно при помощи `this.$store.dispatch('xxx')`, или используя вспомогательную функцию `mapActions`, создающую локальные псевдонимы для действий в виде методов компонента (требуется наличие корневого `$store`): |
| 102 | + |
| 103 | +``` js |
| 104 | +import { mapActions } from 'vuex' |
| 105 | + |
| 106 | +export default { |
| 107 | + // ... |
| 108 | + methods: { |
| 109 | + ...mapActions([ |
| 110 | + 'increment' // проксирует this.increment() в this.$store.dispatch('increment') |
| 111 | + ]), |
| 112 | + ...mapActions({ |
| 113 | + add: 'increment' // проксирует this.add() в this.$store.dispatch('increment') |
| 114 | + }) |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +### Композиция действий |
| 120 | + |
| 121 | +Раз действия зачастую асинхронны, то как узнать, что действие уже завершилось? И, что важнее, как быть со связанными между собой действиями при организации более сложных асинхронных потоков? |
| 122 | + |
| 123 | +Для начала стоит вспомнить, что `store.dispatch` возвращает значение, равное результату вызванного обработчика действия, что позволяет использовать Promise: |
| 124 | + |
| 125 | +``` js |
| 126 | +actions: { |
| 127 | + actionA ({ commit }) { |
| 128 | + return new Promise((resolve, reject) => { |
| 129 | + setTimeout(() => { |
| 130 | + commit('someMutation') |
| 131 | + resolve() |
| 132 | + }, 1000) |
| 133 | + }) |
| 134 | + } |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +Теперь можно сделать так: |
| 139 | + |
| 140 | +``` js |
| 141 | +store.dispatch('actionA').then(() => { |
| 142 | + // ... |
| 143 | +}) |
| 144 | +``` |
| 145 | + |
| 146 | +А в другом действии — так: |
| 147 | + |
| 148 | +``` js |
| 149 | +actions: { |
| 150 | + // ... |
| 151 | + actionB ({ dispatch, commit }) { |
| 152 | + return dispatch('actionA').then(() => { |
| 153 | + commit('someOtherMutation') |
| 154 | + }) |
| 155 | + } |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +И, в конце концов, используя [async / await](https://tc39.github.io/ecmascript-asyncawait/) — возможности, которые вот-вот станут общедоступными, можно компоновать действия таким образом: |
| 160 | + |
| 161 | +``` js |
| 162 | +// предположим, что getData() и getOtherData() возвращают промисы |
| 163 | + |
| 164 | +actions: { |
| 165 | + async actionA ({ commit }) { |
| 166 | + commit('gotData', await getData()) |
| 167 | + }, |
| 168 | + async actionB ({ dispatch, commit }) { |
| 169 | + await dispatch('actionA') // дожидаемся завершения действия actionA |
| 170 | + commit('gotOtherData', await getOtherData()) |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +> `store.dispatch` может вызывать несколько обработчиков действий в различных модулях одновременно. В этом случае возвращаемым значением будет Promise, разрешающийся после разрешения всех вызванных обработчиков. |
0 commit comments