Как составить редукторы редукции с зависимым состоянием
Я работаю над приложением React/Redux, которое позволяет добавлять "виджеты" на страницу и манипулировать в 2D-пространстве. Это требование, что сразу можно выбирать и манипулировать несколькими виджетами. Упрощенная версия моего текущего дерева состояний выглядит примерно так:
{
widgets: {
widget_1: { x: 100, y: 200 },
widget_2: { x: 300, y: 400 },
widget_3: { x: 500, y: 600 }
},
selection: {
widgets: [ "widget_1", "widget_3" ]
}
}
В настоящее время это дерево управляется двумя редукторами, одним управляющим состоянием widgets
и другим управляющим состоянием selection
. Редуктор состояния выбора можно упростить как (примечание: я также использую Immutable.js)...
currentlySelectedWidgets(state = EMPTY_SELECTION, action) {
switch (action.type) {
case SET_SELECTION:
return state.set('widgets' , List(action.ids));
default:
return state
}
}
Все это работает очень хорошо, но теперь у меня есть требование, что я изо всех сил пытаюсь вписаться в эту модель...
Для целей пользовательского интерфейса мне нужно, чтобы текущий выбор имел свойство x/y, которое отражает верхний левый угол выбранных в данный момент координат x/y. Пример состояния может выглядеть как...
{
widgets: {
widget_1: { x: 100, y: 200 },
widget_2: { x: 300, y: 400 },
widget_3: { x: 500, y: 600 }
},
selection: {
x: 100,
y: 200,
widgets: [ "widget_1", "widget_3" ]
}
}
Логика здесь довольно проста, например. найдите min()
для всех выбранных значений виджетов x
и y
, но я не уверен, как я должен справиться с этим в плане состава редуктора, поскольку редуктор currentlySelectedWidgets
не имеет доступа к части widgets
дерева состояний. Я рассмотрел...
- объединение редукторов в один редуктор: это не похоже на большое масштабируемое решение.
- передача текущего списка виджетов с помощью действия: это кажется особенно неприятным.
- найти лучший способ моделирования дерева состояний: у меня было мнение, но любые альтернативные версии, похоже, имеют другие недостатки.
- Построение пользовательского
combineReducers()
, который может подать список виджетов в мой редуктор currentlySelectedWidgets()
в качестве дополнительного аргумента: я думаю, что это может быть моим лучшим выбором.
Я очень хочу услышать любые другие предложения о том, как другие управляют подобными ситуациями, похоже, что управление "текущим выбором" должно быть проблемой общего состояния, которую другие должны были решить.
Ответы
Ответ 1
Это хороший прецедент для Reselect:
Создайте селектор для доступа к вашему состоянию и возврата полученных данных.
В качестве примера:
import { createSelector } from 'reselect'
const widgetsSelector = state => state.widgets;
const selectedWidgetsSelector = state => state.selection.widgets;
function minCoordinateSelector(widgets, selected) {
const x_list = selected.map((widget) => {
return widgets[widget].x;
});
const y_list = selected.map((widget) => {
return widgets[widget].y;
});
return {
x: Math.min(...x_list),
y: Math.min(...y_list)
};
}
const coordinateSelector = createSelector(
widgetsSelector,
selectedWidgetsSelector,
(widgets, selected) => {
return minCoordinateSelector(widgets, selected);
}
);
Теперь coordinateSelector
предоставляет доступ к вашим свойствам min x
и y
. Вышеуказанный селектор будет обновляться только после изменения состояния widgetsSelector
или selectedWidgetsSelector
, что делает его очень эффективным способом доступа к свойствам, которые вы используете, и избегает дублирования в дереве состояний.
Ответ 2
Если вы используете промежуточное ПО thunk (https://github.com/gaearon/redux-thunk), вы можете сделать действие SET_SELECTION
thunk, что позволит ему читать все состояние перед отправкой, которая будет получена вашим редуктором.
// action creator
function setSelection(selectedWidgetId) {
return (dispatch, getState) => {
const {widgets} = this.getState();
const coordinates = getSelectionCoordinates(widgets, selectedWidgetIds);
dispatch({
type: SET_SELECTION,
payload: {
widgets: selectedWidgets,
x: coordinates.x,
y: coordinates.y
}
});
}
Таким образом, вы получаете всю необходимую информацию в своем редукторе выбора, не переходя по списку всех объектов виджетов к вашему действию.
Ответ 3
Я использую это:
CommonReducer.js
export default function commonReducer(state = initialState.doesNotMatter, payload) {
switch (payload.type) {
case "JOINED_TYPE": {
let newState = Object.assign({}, state);
return newState;
}
default:
return state;
}
}
SomeOtherReducer.js
import commonReducer from './commonReducer';
export default function someReducer(state = initialState.somePart, payload) {
switch (payload.type) {
case "CUSTOM_TYPE": {
let newState = Object.assign({}, state);
return newState;
}
default:{
return commonReducer(state, payload);
}
}
}