# Создание 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://max-frontend.gitbook.io/redux-course-ru/struktura_prilozheniya/sozdanie_actions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
