undo和redo

undo和redo是在做一次笔试题的时候遇到的,最近在用redux重构todos的时候,又出现了,这就写一下吧。



在应用中构建撤销和重做功能不是很简单,因为,面对这两个问题,你需要克隆所有相关的model来追踪每一个历史状态,还需要考虑整个撤销堆栈,用户的初始更改也是可以撤销的。



在redux中可以轻松的实现撤销,因为:

  1. 我们在redux应用中,只需要关心state的子树;

  2. state也是不可变数据,所有修改被描述成独立的action,action与预期的撤销堆栈模型很接近了;

  3. reducer的签名(state,action)=>state可以很自然的实现reducer enhancers或者higher order reducers.。它们会在我们为reducer添加额外的功能的时候保持着这个签名。
    (以上粘自官网,hhh)

如果我们希望在一个应用在中实现撤销和重做,我们应该保存更多的state来解决问题:

  1. 撤销或重做之后的状态?
  2. 当前状态?
  3. 撤销堆栈中过去和未来的状态是什么?

根据这些,我们设计它们的算法:

{
   past:Array<T>,
   present:T,
   future:Array<T>
}

undo:撤销

  1. 移除past中的最后一个元素
  2. 将上一步移除的元素赋予present.
  3. 将原来的present插入到future的最前面。

redo:重做

  1. 移除future中的第一个元素。
  2. 将上一步移除的元素赋予present.
  3. 将原来present追加到past的最后面。

处理其他Action

  1. 将当前present追加到past的最后面。
  2. 处理完action所产生的新的state赋予present.
  3. 清空future.

Reducer Enhancer

function undoable() {
    //用一个空的action来调用reducer来产生初始的state;
    const initialState = {
        past: [],
        present: reducer(undefined, {}),
        future: []
    };

    //返回一个可以执行撤销和重做的新的reducer

    return function (state = initialState, action) {
        const {past, present, future} = state;

        switch (action.type) {
            case "UNDO":
                const previous = past[past.length - 1];
                const newPast = past.slice(0, past.length - 1);
                return {
                    past: newPast,
                    present: previous,
                    future: [present, ...future]
                };
            case"REDO":
                const next = future[0];
                const newFuture = future.slice(1);
                return {
                    past: [...past, present],
                    present: next,
                    future: newFuture
                };
            default:
                //将其他action委托给原始的reducer处理
                const newPresent = reducer(present, action);
                if (present === newPresent) {
                    return state;
                }
                return {
                    past: [...past, present],
                    present: newPresent,
                    future: []
                };
        }
    }
}

现在我们就可以将任意的reducer通过这个undoable()进行封装,从而让它们可以处理undo和redo这两个action.


redux undo

使用:npm install --save redux-undo
下面将以todo为例,编写reducer.

扫描二维码关注公众号,回复: 2999745 查看本文章
import undoable,{includeAction} from 'redux-undo';

const todo = (state, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                id: action.id,
                text: action.text,
                completed: false
            };
        case "TOGGLE_TODO":
            if (state.id !== action.id) {
                return state;
            }
            return {
                ...state,
                completed: !state.completed
            };
        default:
            return state
    }
};

const todos = (state = [], action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return [...state, todo(undefined, action)];
        case  'TOGGLE_TODO':
            return state.map(t => todo(t, action));
        default:
            return state;
    }
};

const undoableTodos = undoable(todos, {filter: includeAction(['ADD_TODO', 'TOGGLE_TODO'])})

export default undoableTodos;

注1: distinctState()过滤器会过滤掉没有引起state变化的action.
注2:当你要恢复一个state时,必须把present追加到它上面,并且需要通过检查.past.length.future.length 才能确定撤销和重做是否被使用。

//判断可否进行UndoRedo操作
const mapStateToProps = (state) => ({
    canUndo: state.todos.past.length > 0,
    canRedo: state.todos.future.length > 0
});

然后就是一系列渲染等等的操作,代码Github上,可以看看。

结束!

猜你喜欢

转载自blog.csdn.net/qq_39083004/article/details/80426335