Redux
Валерий Кузьмин, Kontur.Recognition, 2018
Дерево комопнентов в React
A
B
C
D
value
onValueChange
value
onValueChange
value
- Sharing
- Perf
Дерево комопнентов в React + Redux
A
C
D
select(value)
onValueChange
onValueChange
select(value)
Redux
Концепции Redux
- Данные и управление ими полностью отделены от представления
- Данные - простой JSON
- Изменения детектируются shallow-checking
- Чтение и запись разделены (CQRS)
- Все синхронно
Мир Redux
Store
Reducer 1
Reducer 2
ActionCreator1
ActionCreator2
Action1
Action2
React
Чтение, подписка (render)
Запись (events)
State
Actions
- Простые объекты
- Обязаны содержать поле type
- Могут содержать другую информацию, обычно поле payload
- type - строка или enum, обычно выделяется в ActionTypes.js
Reducers
(state, {type, payload}) => {
swtich(type)
{
case ActionTypes.DOCUMENT_CHANGED:
return {
...state,
[payload.documentId]: payload.documentData
}
default:
return state;
}
}
(state, action) => newState
combineReducers
createStore(
combineReducers(
{
documents: documentsReducer,
filters: filtersReducer,
}
)
)
Проблема: shallow equality
// nothing changed
state[payload.documentId].field = 123;
return state;
// invalidate all
state[payload.documentId].field = 123;
return _.clone(state);
// too much nesting
return {
...state,
[payload.documentId]: {
...state[payload.documentId],
field: payload.changedField
}
}
Варианты: shallow equality
- Максимально плоское состояние - нормализация
- Использование immutable.js или аналогов
- Забить и обновлять все
Actions
- Команды (REFRESH_LIST)
- Сложная импертивная асинхронная логика
- Обычно не меняют состояние
- События (LIST_REFRESHED)
- Простые изменения состояния
- Результаты команд
ActionCreators: events
(data) => ({type, payload})
(claimItem, documentId) => ({
type: ActionTypes.DOCUMENT_CLAIM_ITEM_CHANGED,
payload: {claimItem, documentId}
})
ActionCreators: commands via redux-thunk
(data) => (dispatch, getState) => {...}
(claimItem, documentId) => async (dipatch, getState) => {
dispatch({type: "CHANGE_STARTED"});
try {
await serverApi.update({documentId, claimItem});
dispatch({type: "CHANGE_SUCCESS"});
} catch (error) {
dispatch({type: "CHANGE_ERROR", payload: {error}})
} finally {
dispatch({type: "CHANGE_ENDED"})
}
}
ActionCreators: commands via redux-saga
(data) => iterable
import {put, call} from 'redux-saga/effects';
function* updateClaimItem(command) {
const {claimItem, documentId} = command.payload;
try {
yield put({type: "CHANGE_STARTED"});
yield call(serverApi.updateClaimItem,
claimItem, documentId);
yield put({type: "CHANGE_SUCCESS"});
} catch (error) {
yield put({type: "CHANGE_ERROR", payload: {error}});
} finally {
yield put({type: "CHANGE_ENDED"});
}
}
Связь с react: react-redux
const DocumentsListImpl = ({documentIds, currentFilter, onRefresh}) => {
return (
<List header={currentFilter}>
<Button onClick={onRefresh} text="обновить"/>
{documentsIds.map(id => <Document key={id} id={id}/>)}
</List>
);
};
const DocumentsList = connect(
function mapStateToProps(state, ownProps) {
const documentIds = state.documents
.byFilter[ownProps.currentFilter]
.map(doc => doc.id);
return {
documentIds
}
},
function mapDispatchToProps(dispatch) {
return {
onRefresh: () => dispatch(refreshActionCreator())
}
}
)(DocumentsListImpl)
export default DocumentsList;
// App.js
<DocumentsList currentFilter="отправленные"/>
Плюсы и минусы
- Оптимизация рендеринга (если правильно написать)
- Тупой, понятный код, хорошо разделенные модель и представление
- Предсказуемое поведение приложения
- Крутые DevTools, с возможностью time-travel
- Кошмар как многословно
- Какие-то новые термины
- Постоянные проблемы с shallow equals
redux-at-krec
By Valeriy Kuzmin
redux-at-krec
- 537