13

我正在构建一个应用程序,在用户向下滚动时执行操作。如果我可以在用户再次向上滚动时撤消这些操作,那就太好了,基本上将滚动变成了一种浏览操作时间线的方式。

Redux 中是否有内置方法可以做到这一点?还是我必须为此编写中间件?

4

4 回答 4

12

Redux 中是否有内置方法可以做到这一点?还是我必须为此编写中间件?

在这种情况下,中间件听起来像是错误的想法,因为这纯粹是状态管理问题。相反,您可以编写一个函数,该函数接受一个 reducer 并返回一个 reducer,并在此过程中通过动作历史跟踪来“增强”它。

我在这个答案中概述了这种方法,它类似于redux-undo 的工作方式,只是你可以存储动作而不是存储状态。(取决于您想要做出的权衡,以及能够以与发生顺序不同的顺序“取消”操作是否重要。)

于 2015-10-03T12:48:47.267 回答
4

我相信这个想法与其说是“撤消”,不如说是在每次操作通过 redux 时保存对整个状态树的引用。

您将拥有一个由不同时间的应用程序状态组成的历史堆栈。

let history = [state1, state2, state3]

// some action happens

let history = [state1, state2, state3, state4]

// some action happens

let history = [state1, state2, state3, state4, state5]

// undo an action

let history = [state1, state2, state3, state4]

state = state4

要“撤消”操作,只需将应用程序状态替换为已保存状态之一即可。

这可以通过支持结构共享的数据结构来提高效率,但在开发中,我们实际上并不需要过多考虑资源限制。

于 2015-09-13T04:42:22.907 回答
2

我还想创建一个简单的撤消功能,但已经发布了一个带有redux-storage的应用程序,它可以为每个用户序列化和加载状态。所以为了保持向后兼容,我不能使用任何包装我的状态键的解决方案,就像redux-undopast: []and 所做的那样present:

寻找替代方案,Dan 的教程启发我重写combineReducers. 现在我有了状态的一部分:history它保存了状态其余部分的最多 10 个副本,并在UNDO操作中弹出它们。这是代码,这也可能适用于您的情况:

function shouldSaveUndo(action){
  const blacklist = ['@@INIT', 'REDUX_STORAGE_SAVE', 'REDUX_STORAGE_LOAD', 'UNDO'];

  return !blacklist.includes(action.type);
}

function combineReducers(reducers){
  return (state = {}, action) => {
    if (action.type == "UNDO" && state.history.length > 0){
      // Load previous state and pop the history
      return {
        ...Object.keys(reducers).reduce((stateKeys, key) => {
          stateKeys[key] = state.history[0][key];
          return stateKeys;
        }, {}),
        history: state.history.slice(1)
      }
    } else {
      // Save a new undo unless the action is blacklisted
      const newHistory = shouldSaveUndo(action) ?
        [{
          ...Object.keys(reducers).reduce((stateKeys, key) => {
            stateKeys[key] = state[key];
            return stateKeys;
          }, {})
        }] : undefined;

      return {
        // Calculate the next state
        ...Object.keys(reducers).reduce((stateKeys, key) => {
          stateKeys[key] = reducers[key](state[key], action);
          return stateKeys;
        }, {}),
        history: [
          ...(newHistory || []),
          ...(state.history || [])
        ].slice(0, 10)
      };
    }
  };
}


export default combineReducers({
  reducerOne,
  reducerTwo,
  reducerThree
});

对我来说,这就像一种魅力,只是看起来不太漂亮。如果这是一个好/坏主意以及为什么,我会很高兴收到任何反馈;-)

于 2015-12-23T15:47:10.960 回答
1

没有内置的方法可以做到这一点。但是你可以从 redux-dev-tools 的工作原理中得到启发(https://github.com/gaearon/redux-devtools)。它基本上具有“时间旅行”功能,它通过跟踪所有动作并每次重新评估它们来工作。因此,您可以轻松浏览所有更改。

于 2015-09-11T16:05:11.213 回答