Как поместить методы на объекты в состоянии 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)))
}
}