# Redux
## Зачем?
Вы могли заметить, что **React** *не предоставляет* (но и не навязывает) способов работы со
слоем данных и/или бэкэндом на фронте. Поток данных в **JSX** через `props` не подходит для этого,
так как одна и та же информация с бэка (например о текущем залогиненном пользователе) может применяться
в отображении во множестве компонентов разных размеров и глубины вложенности. Попытки передать
такую информацию через `props` из главного компонента `App` привели бы к сильному загрязнению
кода **JSX** всякими `props` с данными и множественной копипасте.
### Компоненты и хранилище.
Если вы сделали ДЗ, то вы смогли организовать **AJAX** к бэку напрямую из компонентов.
Для несложных проектов из малого количества компонентов этот подход имеет право на жизнь
и неплохо ложится на жизненный путь компонента. Однако в более сложных интерфейсах
однотипные запросы будут в разных компонентах, да и в целом *компоненты должны влиять друг на друга*.
*Попытки* **напрямую** *общаться между компонентами* (например через колбэки или методы) приводят
к **спагетти-коду**, так как "связей" в коде оказывается слишком много, и эта паутина сложно
отлаживается и поддерживается в дальнейшем.
Таким образом, нужен механизм общего хранилища информации о **состоянии всего приложения**, общедоступный для любого
из компонентов, в независимости от его размера и глубины вложенности.
При наличии подобной **общей точки** для обмена данными между бэком и компонентами (и компонентами между собой)
количество связей между частями приложения *меньше* чем при прямом обращении компонент-компонент.
**Связи** при наличии хранилища предполагают разделение ответственности:
- Новые данные посылаются в хранилище потому что *так надо*; компонент, посылающий такое событие не волнует, как именно это изменение будет обработано.
- Хранилище *знает*, как правильно обработать новые данные; Хранилищу *не важно*, **откуда** эти данные пришли, и куда уйдут;
- Все компоненты, которые *подписаны* на этот вид данных, занимаются отображением и/или изменением логики своей работы, *не заморачиваясь с тем,
откуда этот сигнал пришел*.
Такой подход позволяет гибко настраивать и более прозрачно отслеживать потоки данных внутри приложения.
## Как?
- Само хранилище реализуется объектом, запрещенным к изменению напрямую;
- Запрос на изменение хранилища присылается в форме объекта (**action**), который обрабатывается специальной функцией-**редьюсером**.
- Редьюсер создает **новый объект** хранилища и возвращает его.
- Хранилище обновляется и *оповещает подписчиков*.
**Итого**: минимальная реализация **redux** занимает строк 50.
## React
Для связывания компонентов с **redux** используется подход **HOC** - **high-order components**, аналог
функций высшего порядка, только для компонентов. Суть в том, что на базе вашего компонента
создается компонент-обертка, которая передает в ваш компонент нужные части хранилища
и функции для изменения хранилища используя `props`. Таким образом компонент
*автоматически* отображает изменения в хранилище и имеет возможность послать
запрос на изменение в хранилище.
## Redux Minimal
```bash
npm install --save redux react-redux
```
```jsx
import {Provider, connect} from 'react-redux';
import {createStore, combineReducers} from 'redux';
let store = createStore((state, action) => { //единственный редьюсер данного хранилища
if (state === undefined){ //redux запускает редьюсер хотя бы раз, что бы инициализировать хранилище
return {counter: 0}; //обязательно вернуть новый объект, а не изменить текущий state
}
if (action.type === 'COUNTER_INC'){ //в каждом action должен быть type
return {counter: state.counter +1} //создаем новый объект базируясь на данных из предыдущего состояния
}
if (action.type === 'COUNTER_DEC'){
return {counter: state.counter -1}
}
return state; //редьюсеров может быть несколько, в таком случае вызываются все редьюсеры, но далеко не всегда action.type будет относится к этому редьюсеру. Тогда редьюсер должен вернуть state как есть.
})
store.subscribe(()=> console.log(store.getState())) // подписка на обновления store
store.dispatch({
type: 'COUNTER_INC'
})
store.dispatch({
type: 'COUNTER_DEC'
})
```
- `createStore` создаёт новое хранилище. В качестве параметра передается *одна функция-редьюсер*, которая обрабатывает все запросы на изменение хранилища
- **Редьюсер** принимает текущее состояние хранилища и объект `action` - действие над хранилищем.
- Когда запускается `createStore` редьюсер запускается с `state = undefined` для первоначальной инициализации хранилища
- В `action` обязательно должно быть поле `type`
- **Редьюсер** обязан возвращать каждый раз *новый объект*, а не модифицировать старый.
- Если редьюсер не знает переданный тип действия, он должен вернуть `state` как есть.
- метод `subscribe` позволяет добавить любое количество колбэков, которые будут вызваны после изменения хранилища
- метод `dispatch` посылает **объект-действие** в хранилище для обработки **редьюсером**
## Redux usual.
В обычной ситуации один редьюсер с большим количеством действий смотрится не очень. Посему в комплекте с **redux** идет функция
`combineReducers`, которая:
- воспринимает не один редьюсер (как `createStore`), а объект, ключами в котором являются ветви хранилища, а значениями - редьюсеры, работающие с этой ветвью.
- Редьюсеры получают не весь `state`, а только свою ветвь.
- Редьюсер возвращает не весь `state`, а только свою ветвь.
- Однако редьюсеры получают **все** действия: и свои, и чужие. Посему не забываем возвращать `state` неизменным если действие не относится к этому редьюсеру.
```jsx
import {Provider, connect} from 'react-redux';
import {createStore, combineReducers} from 'redux';
let counterReducer = (state, action) => { //один из редьюсеров данного хранилища
if (state === undefined){ //redux запускает редьюсер хотя бы раз, что бы инициализировать хранилище
return {counter: 0}; //обязательно вернуть новый объект, а не изменить текущий state
}
if (action.type === 'COUNTER_INC'){ //в каждом action должен быть type
return {counter: state.counter +1} //создаем новый объект базируясь на данных из предыдущего состояния
}
if (action.type === 'COUNTER_DEC'){
return {counter: state.counter -1}
}
return state; //редьюсеров может быть несколько, в таком случае вызываются все редьюсеры, но далеко не всегда action.type будет относится к этому редьюсеру. Тогда редьюсер должен вернуть state как есть.
}
let booleanReducer = (state, action) => { //второй редьюсер
if (state === undefined){
return {boolean: false}
}
if (action.type === 'BOOLEAN_SET'){
return {boolean: true}
}
if (action.type === 'BOOLEAN_RESET'){
return {boolean: false}
}
if (action.type === 'BOOLEAN_TOGGLE'){
return {boolean: !state.boolean}
}
return state; //редьюсеров может быть несколько, в таком случае вызываются все редьюсеры, но далеко не всегда action.type будет относится к этому редьюсеру. Тогда редьюсер должен вернуть state как есть.
}
const reducers = combineReducers({ //создаем функцию-обертку, которая запустит последовательно counterReducer и booleanReducer передав им ветви c и b хранилища и обновив эти же ветви в случае нового состояния.
c: counterReducer,
b: booleanReducer
})
const store = createStore(reducers);
store.subscribe(()=> console.log(store.getState())) // подписка на обновления store, теперь тут две ветви.
store.dispatch({
type: 'COUNTER_INC'
})
store.dispatch({
type: 'BOOLEAN_SET'
})
store.dispatch({
type: 'COUNTER_DEC'
})
store.dispatch({
type: 'BOOLEAN_TOGGLE'
})
```
Как видно из примера выше, редьюсер, когда он один, и редьюсер, который передается одним из многих в `combineReducers` являются одинаковыми. Единственное отличие в том, что для
`combineReducers` обязательно быть готовым что ни один из if не отработает, так как тип действия будет для другого редьюсера. В случае с одним редьюсером это не важно.
## actionCreators
По феншую вы не отправляете объект действия непосредственно литеральным параметром `dispatch`, а создаете функцию, которая на базе нужной информации
возвращает объект действия. Это удобно при работе с компонентами **React**.
```jsx
function actionInc(){
return {
type: 'COUNTER_INC'
}
}
function actionDec(){
return {
type: 'COUNTER_DEC'
}
}
store.dispatch(actionInc())
store.dispatch(actionDec())
```
## React-Redux
Компоненты связываются с **redux** гибким способом:
- Все компоненты, которые вы хотите присоединить к тому или иному хранилищу должны находится внутри
`