Как поместить методы на объекты в состоянии Redux?

В соответствии с docs состояние реактивного приложения должно быть чем-то сериализуемым. Как насчет классов?

Скажем, у меня есть приложение ToDo. Каждый из элементов Todo имеет такие свойства, как name, date и т.д. До сих пор так хорошо. Теперь я хочу иметь методы для объектов, которые не являются сериализуемыми. То есть Todo.rename(), который переименует todo и сделает много других вещей.

Насколько я понимаю, функция может быть объявлена ​​где-то и rename(Todo) или, возможно, передать эту функцию через реквизит this.props.rename(Todo) в компонент.

У меня есть 2 проблемы с объявлением .rename() где-то: 1) Где? В редукторе? Было бы трудно найти все методы would be instance где-нибудь в редукторах вокруг приложения. 2) Передача этой функции. В самом деле? должен ли я вручную передать его со всех компонентов более высокого уровня через И каждый раз, когда у меня есть больше методов, добавьте тонну шаблона, чтобы просто сдать его? Или всегда делайте и надейтесь, что у меня есть только один метод переименования для одного типа объектов. Не Todo.rename() Task.rename() и Event.rename()

Это кажется мне глупым. Объект должен знать, что с ним можно сделать и каким образом. Не правда ли?

Что мне здесь не хватает?

Ответ 1

В Redux у вас действительно нет пользовательских моделей. Ваше состояние должно быть простым объектом (или неизменяемыми записями). Ожидается, что у них не будет каких-либо пользовательских методов.

Вместо того, чтобы вводить методы в модели (например, TodoItem.rename), вы должны писать редукторы, которые обрабатывают действия. Что все в Redux.

// Manages single todo item
function todoItem(state, action) {
  switch (action.type) {
    case 'ADD':
      return { name: action.name, complete: false };

    case 'RENAME':
      return { ...state, name: action.name };

    case 'TOGGLE_COMPLETE':
      return { ...state, complete: !state.complete };

    default:
      return state;
  }
}

// Manages a list of todo items
function todoItems(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [...state, todoItem(undefined, action)];

    case 'REMOVE':
      return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1)
      ];

    case 'RENAME':
    case 'TOGGLE_COMPLETE':
      return [
        ...state.slice(0, action.index),
        todoItem(state[action.index], action),
        ...state.slice(action.index + 1)
      ];
  }
}

Если это по-прежнему не имеет смысла, прочитайте учебник по основам Redux, потому что вы, похоже, ошибочно представляете, как приложения Redux структурированным.

Ответ 2

Ответ Dan, конечно, правильный с точки зрения того, как вы должны использовать redux при написании приложения с нуля. Мой ответ основан на неидеальной мотивирующей ситуации, которая может быть похожа на ситуацию с ОП и как я думаю об этом.

Я пытаюсь создать приложение redux, которое зависит от сторонних библиотек в редукторе redux, которые выполняют операции с входными объектами с помощью методов, и я хотел сохранить эти входные объекты в состоянии redux. Вот моя мотивирующая ситуация в псевдокоде и как я "решаю" его с помощью метода назначения lodash:

// Library A is a large 3rd party library I don't want to change
// Library A expects instances of MyClass to have a method 'complexOperation', and also to have property 'a'
LibraryA = class {
  static reliesOnComplexOperation(myClassInstance) {
    if (myClassInstance.complexOperation() && myClassInstance.a < 100) {
      return true;
    } else {
      return false;
    }
  }
}

// MyClass is my own class that I control, and I want to store it state and make changes to it using the redux reducer.
MyClass = class {
  constructor() {
    this.a = 0;
  }
  myComplexOperation() {
    return a > 10;
  }
}

//and here is my redux reducer, making use of Library A
function reducer(state, action) {
  switch (action.type) {
    case CHANGE_MY_CLASS:
      const myClassInstancePlain = state.myClassInstance;
      // redux has converted myClassInstance into a plain object with no methods, so we need to create a new instance of MyClass, and assign property values from the plain object to the new instance. Using the assign function from lodash library to achieve this - likely not the most efficient way
      const myClassInstance = _.assign(new MyClass(), myClassInstancePlain);

      // now I can pass myClassInstance off to LibraryA, and the complexOperation method will be available
      if (LibraryA.reliesOnComplexOperation(myClassInstance)) {
        myClassInstance.a = 50;
      }
      // I can return myClassInstance as part of the new state, and redux will convert it to a plain object
      return {
        ...state,
        myClassInstance
      };
    default:
      return state;
  }
}

Таким образом, в этом примере показан один из способов включения объектов с методами в состоянии redux в виде простых объектов, а затем добавление обратно методов, чтобы они могли использоваться в редукторе redux. Хотелось бы услышать Дэн или кого-либо еще мысли по этому образцу или как это можно было бы сделать лучше. Я видел сообщение Дэна в нескольких местах, где хранение объектов с помощью методов - плохая идея в редуксе, поэтому цель здесь - найти способ хранения объекта как простого объекта и приложить методы, когда они вам понадобятся эффективным образом.

Ответ 3

Я не уверен, что это точно релевантно (контекст использует Redux с React), но я нашел эту страницу при поиске чего-то подобного, поэтому я пишу это, если это интересно.

Моя мотивация заключается в том, что у меня есть объекты состояния, где я хотел бы представить представление этого состояния, а не самого состояния. Например, (используя Immutable.js), у меня есть состояние (схема), которое содержит одно или несколько полей, каждый из которых идентифицируется уникальным идентификатором и хранится на карте, плюс список идентификаторов, который дает упорядочение полей.

Я действительно не хочу писать такие вещи, как

state.get('field').get(state.get('order').get(nth))

чтобы получить n-е поле в любом месте, кроме близкого к редуктору, так что внутреннее представление скрыто и может быть легко изменено.

То, что я сейчас делаю (и это немного эксперимент), заключается в добавлении функции в тот же файл, что и редуктор:

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => state.get('fields').get(fid)
    }
}

Это используется в соответствующих компонентах React (наряду с аналогично мотивированной функцией schema.bindActionCreators):

export const Schema = connect(
    (state) => schema.mapVisibleState (state),
    (dispatch) => schema.bindActionCreators (dispatch)
)(_Schema) ;

Теперь я могу получить доступ к полям в правильном порядке внутри компонента просто как this.props.getOrderedFields() и не беспокоиться о базовом представлении. Он также обладает дополнительным приятным свойством, что легко выполнить инъекцию зависимостей функции getOrderedFields().

Кроме того, поскольку у меня есть аналогичная структура для состояния поля, я могу расширить это:

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => field.mapVisibleState(state.get('fields').get(fid)),
        getNthField (nth) => field.mapVisibleState(state.get('fields').get(state.get('order').get(nth)))
    }
}