Фон
Некоторое время назад я размахивал своим мозгом о том, как вы могли бы использовать undo/redo в Redux с помощью взаимодействия с сервером (через ajax).
Я придумал решение, используя шаблон команды, где действия регистрируются с помощью методов execute
и undo
в качестве команд, а вместо отправки действия, которые вы отправляете командам. Затем команды сохраняются в стеке и при необходимости подготавливают новые действия.
В моей текущей реализации используется промежуточное программное обеспечение для перехвата рассылок, проверка команд и методов вызова команды и выглядит примерно так:
Middleware
let commands = [];
function undoMiddleware({ dispatch, getState }) {
return function (next) {
return function (action) {
if (action instanceof Command) {
// Execute the command
const promise = action.execute(action.value);
commands.push(action);
return promise(dispatch, getState);
} else {
if (action.type === UNDO) {
// Call the previous commands undo method
const command = commands.pop();
const promise = command.undo(command.value);
return promise(dispatch, getState);
} else {
return next(action);
}
}
};
};
}
Действия
const UNDO = 'UNDO';
function undo() {
return {
type: UNDO
}
}
function add(value) {
return (dispatch, getState) => {
const { counter } = getState();
const newValue = counter + value;
return new Promise((resolve, reject) => {
resolve(newValue); // Ajax call goes here
}).then((data) => {
dispatch(receiveUpdate(data));
});
}
}
function sub(value) {
return (dispatch, getState) => {
const { counter } = getState();
const newValue = counter - value;
return new Promise((resolve, reject) => {
resolve(newValue); // Ajax call goes here
}).then((data) => {
dispatch(receiveUpdate(data));
});
}
}
Команды
class Command {
execute() {
throw new Error('Not Implemented');
}
undo() {
throw new Error('Not Implemented');
}
}
class AddCommand extends Command {
constructor(value) {
super();
this.value = value;
}
execute() {
return add(this.value);
}
undo() {
return sub(this.value);
}
}
приложения
const store = createStoreWithMiddleware(appReducer);
store.dispatch(new AddCommand(10)); // counter = 10
store.dispatch(new AddCommand(5)); // counter = 15
// Some time later
store.dispatch(undo()); // counter = 10
(более полный пример здесь)
Есть несколько проблем, которые я нашел с моим текущим подходом:
- Благодаря внедрению через промежуточное программное обеспечение для всего приложения может существовать только один стек.
- Невозможно настроить тип команды
undo
. - Создание команды для вызова действий, которые в свою очередь возвращают promises, кажется очень запутанным.
- Команды добавляются в стек до завершения действия. Что происходит с ошибками?
- Поскольку команды не находятся в состоянии, нельзя добавлять функции is_undoable.
- Как реализовать оптимистичные обновления?
Справка
Мой вопрос, может ли кто-нибудь предложить лучший способ реализовать эту функциональность в Redux?
Самые большие недостатки, которые я вижу сейчас, - это команды, добавляемые до завершения действий, и как было бы сложно добавить оптимистические обновления в микс.
Любое понимание понимается.