> For the complete documentation index, see [llms.txt](https://max-frontend.gitbook.io/redux-course-ru/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://max-frontend.gitbook.io/redux-course-ru/struktura_prilozheniya/sozdanie_actions.md).

# Создание actions

*ОБНОВЛЕНИЕ 2018: Вышло* [*второе издание*](https://maxfarseer.gitbooks.io/redux-course-ru-v2/content/) *(современный код и версии пакетов, данное издание УСТАРЕЛО)*

*На* [*канале*](http://bit.ly/2IqH74P) *так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы,* [*присоединяйтесь*](http://bit.ly/2IqH74P)*!*

## Создание actions

Наконец-то мы подходим к вопросу взаимодействия с пользователем приложения. Практически любое действие пользователя в интерфейсе = **отправка действия** (*dispatch actions*)

По клику на кнопку года, наше приложение:

* устанавливает заголовок
* загружает фото этого года

Сейчас предлагаю рассмотреть установку заголовка. Загрузка фото требует выполнения асинхронного запроса, а чтобы добраться до этого, мы должны рассмотреть несколько интересных вещей. К тому же, установка заголовка отлично показывает на простом примере, как *вращаются* данные внутри redux-приложения, а именно: 1. Приложение получило изначальное состояние (*initial state*) 2. Пользователь нажав кнопку, отправил действие (*dispatch action*) 3. Соответсвующий редьюсер обновил часть приложения, в согласии с тем, что узнал от действия. 4. Приложение изменилось и теперь отражает новое состояние. 5. ... (все повторяется по кругу, с пункта 2)

Это и есть **однонаправленный** поток данных.

Создадим page action:

*src/actions/PageActions.js*

```javascript
export function setYear(year) {

  return {
    type: 'SET_YEAR',
    payload: year
  }

}
```

Напоминаю, что поля type и payload - всего лишь "негласное" соглашение. Немного об этом, можно почитать на английском [тут](https://github.com/acdlite/flux-standard-action).

Поправим редьюсер page:

*src/reducers/page.js*

```javascript
const initialState = {
  year: 2016,
  photos: []
}

export default function page(state = initialState, action) {

  switch (action.type) {
    case 'SET_YEAR':
      return { ...state, year: action.payload }

    default:
      return state;
  }

}
```

Обратите внимание, в аргументах у функции page указан второй аргумент - *action*. Это стандартные аргументы redux reducer'а. Благодаря этому, мы можем легко обрабатывать различные действия по их типу, попадая в нужную секцию case оператора switch.

Так же обратите внимание, что мы не **изменили** объект state, а вернули **новый** с полем *year* равным *action.payload* (а значит годом, выбранным пользователем).

### Добавляем вызов actions из компонентов

У нас есть *action*, и есть *reducer* готовый изменить *state* приложения (да, я нарочно пишу иногда эти слова по-английски). Но наш компонент не знает как обратиться к необходимому действию.

Согласно таблице из прошлого раздела: для изменения данных, наш *компонент* Page.js, должен вызывать *callback* из *this.props*, а наш контейнер\* App.js - отправлять действие (*dispatch action*).

\* я говорю, контейнер, хотя правильнее называть контейнером `<Connect(App) />`, но так как он генерируется функцией *connect* на основе App.js, считаю это допустимым.

Из документации функции [connect](https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options), нам так же становится ясно, что с помощью этой функции мы можем не только подписаться на обновления данных, но и "прокинуть" наши *actions* в контейнер.

`connect`, первым аргументом принимает "маппинг" (соответствие) state к props, а вторым маппинг dispatch к props. Как бы дико это не звучало, на практике это значит, что нам достаточно передать второй аргумент.

Исправим App.js

*src/containers/App.js*

```javascript
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import * as pageActions from '../actions/PageActions'

class App extends Component {
  render() {
    const { user, page } = this.props
    const { setYear } = this.props.pageActions

    return <div>
      <User name={user.name} />
      <Page photos={page.photos} year={page.year} setYear={setYear} />
    </div>
  }
}

function mapStateToProps(state) {
  return {
    user: state.user,
    page: state.page
  }
}

function mapDispatchToProps(dispatch) {
  return {
    pageActions: bindActionCreators(pageActions, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
```

Начнем с разбора `mapDispatchToProps`. Внутри функции мы использовали вспомогательную функцию из redux - *bindActionCreators* ([офф. документация](http://redux.js.org/docs/api/bindActionCreators.html), которая позволила вызывать setYear, если выразиться просто с некоторыми допущениями как:

```javascript
store.dispatch({
    type: 'SET_YEAR'
    payload: 2016
})
```

Тем самым необходимое изменение прослушивается в redux store, и в нашем редьюсере Page соответственно.

Следовательно, после выполнения `connect(mapStateToProps, mapDispatchToProps)(App)`, мы получили в App.js новые свойства (*props*), что наглядно демонстрирует вкладка "React" в chrome dev tools. ![setYear in dev tools](/files/-LvLnMgm43zq_X0wcme_)

Добавив setYear в свойства Page.js , не составит труда использовать необходимый action из компонента, который по прежнему знать ничего не знает о redux.

[UPDATE](https://habrahabr.ru/post/279249/#comment_8809893) \[18.03.16]: свойство [innerText](https://developer.mozilla.org/ru/docs/Web/API/Node/innerText) приведенное в коде ниже - нестандартное, поэтому с ним могут возникнуть проблемы в некоторых браузерах. Вместо него, вы можете использовать - [textContent](https://developer.mozilla.org/ru/docs/Web/API/Node/textcontent).

*src/components/Page.js*

```javascript
import React, { PropTypes, Component } from 'react'

export default class Page extends Component {
  onYearBtnClick(e) {
    this.props.setYear(+e.target.innerText)
  }
  render() {
    const { year, photos } = this.props
    return <div>
      <p>
        <button onClick={::this.onYearBtnClick}>2016</button>
        <button onClick={::this.onYearBtnClick}>2015</button>
        <button onClick={::this.onYearBtnClick}>2014</button>
      </p>
      <h3>{year} год</h3>
      <p>У тебя {photos.length} фото.</p>
    </div>
  }
}

Page.propTypes = {
  year: PropTypes.number.isRequired,
  photos: PropTypes.array.isRequired,
  setYear: PropTypes.func.isRequired
}
```

Собственно, код компонента Page по прежнему очень простой. Строка `::this.onYearBtnClick` === `this.onYearBtnClick.bind(this)`, и нужна так как React с версии 0.14.x не привязывает this к компоненту.

Использование двойного двоеточия - это возможность ES7 ([experimental](https://github.com/zenparsing/es-function-bind)), которая доступна в babel с настройкой `stage=0` (для тех кто писал код, начиная с раздела "Подготовка" - все уже настроено, смотри файл *.babelrc*)

Глава выдалась достаточно длинной, а хуже всего, что мы написали "кипу" кода, всего лишь для обновления цифры в заголовке. Где профит, как говорится?

Профит обнаружится дальше, когда ваше приложение разрастется. Когда его будет необходимо поддерживать и добавлять новые фичи. За счет **однонаправленного** потока данных (юзер кликнул - действие вызвалось - редьюсер изменил состояние - компонент отрисовал изменения) даже в приложении, написанном давно, у вас получится очень быстро разобраться и внести необходимые обновления, которые требует бизнес. К тому же, такой подход отлично работает и для командной работы.

[Искодный код](https://github.com/maxfarseer/redux-ru-tutorial/tree/create_actions)
