Подтвердить что ты не робот

Как составить редукторы редукции с зависимым состоянием

Я работаю над приложением 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() в качестве дополнительного аргумента: я думаю, что это может быть моим лучшим выбором.

Я очень хочу услышать любые другие предложения о том, как другие управляют подобными ситуациями, похоже, что управление "текущим выбором" должно быть проблемой общего состояния, которую другие должны были решить.

4b9b3361

Ответ 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);
         }
      }
}