Redux
Никита Солоснюк


- Управление состоянием в Реакт
- Флакс
- Редакс
- Стор
- Экшн
- Редьюсер
- Сайд-эффекты
- Реакт + Редакс
- Thunk/Saga
- Hooks
План лекции
Управление состоянием в React
- Состояние компонента (Component State) — state
- Относительное состояние (Relative State) — props
- Переданное состояние (Provided State) — Context API
state = {
issues: issues,
activeTab: ‘open’,
}
<Issue
id={issue.id}
title={issue.title}
/>
Управление состоянием в React
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
Недостатки
- Многоуровневая передача состояния
- Подъем состояния через функции-коллбеки
- Смешение логики и представления
Here's Flux
Чё за флакс?
Flux is a pattern for managing data flow in your application. The most important concept is that data flows in one direction.
Чё за флакс? (2)
технологияфреймворкбиблиотека- паттерн
Концепция

Action ⟶ Dispatcher ⟶ Store ⟶ View
Если необходимо изменить состояние приложения — нужно создать действие (action).
У действия есть тип и полезные данные, с которыми необходимо что-то сделать.
После создания, действие передается диспетчеру (dispatcher).
Action ⟶ Dispatcher ⟶ Store ⟶ View
Чтобы изменить состояние, нужно сгенерировать событие с описанием изменения.
Это делается с помощью диспетчера.
Диспетчер регистрирует ваши хранилища и отправляет действие в нужное хранилище (store).
Action ⟶ Dispatcher ⟶ Store ⟶ View
Хранилище содержит состояние приложения, и только хранилище знает и умеет изменять состояние.
В зависимости от типа действия, хранилище обновляет состояние (действие также содержит новые данные).
После изменения хранилище генерирует событие об изменении. После оно сообщает вашему представлению (view), что состояние изменилось и надо обновиться.
Action ⟶ Dispatcher ⟶ Store ⟶ View
Получает новое состояние и рендерит его.
Представление абсолютно ничего не знает про ваше приложение, всё что ему известно, это переданные данные и способ их отрисовки.
Представление также может генерировать новые действия.
Концепция

А при чём тут Редакс?

Flux
Redux
MobX
Управление состоянием в React
- Внешнее состояние (External State)
Memes

Memes

const store = {}
Redux
А при чём тут Редакс?
Redux - реализация паттерна Flux + улучшения
Основные принципы:
1. Единственный источник истины
2. Read-only
3. Изменения делаются «чистыми» функциями
Cartoon intro to Redux (есть версия на русском)
Единственный источник истины
Только одно хранилище для всего состояния приложения.
Хранилище — объект с несколькими уровнями вложенности.
Пример:
{
num: 5,
}
Единственный источник истины
Пример чуть поболбше:
{
users: {
list: [{
id: 1,
name: 'Mike',
age: '12',
}, {
id: 2,
name: 'Stasyan',
age: '52',
}, {
id: 3,
name: 'Dan',
age: '34',
}]
},
messages: {
list: [{
id: 1,
content: 'Ну ты и вымахал, Стасян!',
authorId: 1,
receiverId: 2,
}, {
id: 2,
content: 'Стасян, скинь домашку, блинб',
authorId: 3,
receiverId: 2,
}]
}
}Read-only
Единственный способ изменить состояние — передать экшен — объект, описывающий, что произошло.
В React изменять состояние можно только с помощью setState().
В Redux — только с помощью экшенов.
Чистые функции
- Детерминированность
- Отсутствие сайд-эффектов
console.log(`Hello`);
const suc = a => b => a * b;
const rand = a => Math.random();
const getData = url => fetch(url);Чистые функции

like props in React


Redux
Redux
function createStore(reducer, initialState) {
let state = initialState
return {
dispatch: action => { state = reducer(state, action) },
getState: () => state,
}
}Пример
Redux

Action
Просто объект с обязательным полем type
store.dispatch({
type: 'INCREMENT',
value: 42
})Reducer
Reducer - чистая функция принимающая state и action и возвращающая новый state
function reducer(state = { num: 69 }, action) {
switch (action.type) {
case 'INCREMENT':
return { num: state.num + action.num };
default:
return state;
}
Side-effects
Middleware
Мидлвейр - просто функция, которая принимает стор, следующий мидлвейр и ваш экшен.
Выполняет какое-то действие (например, выводит в консоль ваш экшен).
Передает выполнение следующему мидлвейверу (если это последний, то экшен попадает в редьюсер).
Middleware
const log = store => next => action => {
console.log(action);
next(action);
};
const store = createStore(reducer, applyMiddleware(log));
redux-thunk
import thunk from `redux-thunk`;
const store = createStore(reducer, applyMiddleware(thunk));
store.dispatch(dispatch => {
fetch(`something.com`)
.then(data => dispatch({ type: 'DATA', data }))
.catch(err => dispatch({ type: 'ERR', err }));
});
redux-saga
Альтернативный подход к организации сайд-эффектов. Вместо того, чтобы диспатчить функции, которые обрабатываются redux-thunk-ом, вы создаёте сагу, которая собирает всю логику обработки внутрь себя. В отличие от thunk-ов, которые выполняются, когда вы их диспатчите, саги запускаются при старте приложения и как бы «работают в фоне». Саги слушают все экшены, которые диспатчит стор, и решают, что делать с ними.
И у саг в редаксе два преимущества по сравнению с thunk-ами:
— Они позволяют организовывать сложные последовательности сайд-эффектов
— И они очень легко тестируются
redux-saga
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
/*
Starts fetchUser on each dispatched `USER_FETCH_REQUESTED` action.
Allows concurrent fetches of user.
*/
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
/*
Alternatively you may use takeLatest.
Does not allow concurrent fetches of user. If "USER_FETCH_REQUESTED" gets
dispatched while a fetch is already pending, that pending fetch is cancelled
and only the latest one will be run.
*/
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;combineReducers
import { createStore, combineReducers } from 'redux';
const userReducer = (state = {}, action) => { … };
const widgetReducer = (state = {}, action) => { … };
// Создание одного большого редьюсера
const reducers = combineReducers({ userReducer, widgetReducer });
const store = createStore(reducers);Mème Classique

Action Creator
const getDataActionCreator = url =>
dispatch => {
fetch(url)
.then(data => dispatch({ type: 'DATA', data }))
.catch(err => dispatch({ type: 'ERR', err }));
});
React + Redux (koa-react-starter)
Redux hooks
- useSelector
- useDispatch
- useStore
useSelector
const result: any = useSelector(selector: Function, equalityFn?: Function)import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}useDispatch
const dispatch = useDispatch()import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}useStore
const store = useStore()import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update
// if the store state changes
return <div>{store.getState()}</div>
}Rest memes!


09 - Redux
By Startup Summer
09 - Redux
- 163