代码展示

const items = [];
const poppedItems = [];

const add = () => {
  items.push(1);
};

const undo = () => {
  const poppedItem = items.pop();
  poppedItems.push(poppedItem);
};

const redo = () => {
  const poppedItem = poppedItems.pop();
  items.push(poppedItem);
};

原理分析

通过上面代码我们发现,撤销和重做本质上就是对数组的入栈和出栈操作。要注意的一点是,实现重做的前提是在撤销的时候,需要将移除的元素有序的存起来,这样在重做的时候才能用数据可用。

撤销:
将元素按照加入数组的顺序反向移除(最后加入的元素先出)。

重做:
将数组移除的元素按照移除的顺序反向加入(最后移除的元素先入)。

react版本实现

import { useState } from 'react';

const style = {
  height: '100vh',
};

const pointStyle = {
  position: 'absolute',
  width: '10px',
  height: '10px',
  background: 'blue',
};

function App() {
  const [points, setPoints] = useState([]);
  const [popped, setPopped] = useState([]);

  const onClick = (e) => {
    setPoints((points) => [
      ...points,
      {
        x: e.pageX,
        y: e.pageY,
      },
    ]);
  };

  const onUndo = (e) => {
    // 避免按钮上的点击事件冒泡到容器上
    e.stopPropagation();

    // 生成副本
    const nextPoints = [...points];
    // 取出数组中最前面的元素,并且保存到poppedPoint中
    const poppedPoint = nextPoints.pop();
    // 将取出的元素保存到popped中
    setPopped((popped) => [...popped, poppedPoint]);
    // 保存更新后的数组
    setPoints(nextPoints);
  };

  const onRedo = (e) => {
    // 避免按钮上的点击事件冒泡到容器上
    e.stopPropagation();

    // 生成副本
    const nextPopped = [...popped];
    // 取出数组中最前面的元素,并且保存到poppedPoint中
    const poppedPoint = nextPopped.pop();
    // 保存更新后的数组
    setPopped(nextPopped);
    // 将取出的元素保存到popped中
    setPoints((points) => [...points, poppedPoint]);
  };

  return (
    <div style={style} onClick={onClick}>
      <button disabled={points.length < 1} onClick={onUndo}>
        undo
      </button>
      <button disabled={popped.length < 1} onClick={onRedo}>
        redo
      </button>
      {points.map((item) => (
        <div style={{ ...pointStyle, top: item.y, left: item.x }} />
      ))}
    </div>
  );
}

export default App;

热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。