Redux

Валерий Кузьмин, Kontur.Recognition, 2018

Дерево комопнентов в React

A

B

C

D

value

onValueChange

value

onValueChange

value

  1. Sharing
  2. Perf

Дерево комопнентов в React + Redux

A

C

D

select(value)

onValueChange

onValueChange

select(value)

Redux

Концепции Redux

  1. Данные и управление ими полностью отделены от представления
  2. Данные - простой JSON
  3. Изменения детектируются shallow-checking
  4. Чтение и запись разделены (CQRS)
  5. Все синхронно

Мир 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

  1. Максимально плоское состояние - нормализация
  2. Использование immutable.js или аналогов
  3. Забить и обновлять все

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

  • 535