React Hook 实现撤销/重做
如果你想开发像FormBlob这样的无代码工具,那么撤销和重做功能是必不可少的。为什么呢?想象一下,你正在使用一款图像编辑软件,并对画布进行了多次修改。过了一段时间后,你意识到之前的版本比现在的版本看起来好得多。这时,你会撤销操作,直到达到你满意的程度。
如果软件没有撤销/重做功能,你很可能会破口大骂,然后永远放弃这款软件。
那么,我们如何实现撤销/重做功能,防止用户放弃我们的应用呢?
先决条件
如果您还不熟悉 React Hooks,我建议您先阅读一下相关资料(链接在此)。其中最基础的 Hook 之一是 React 内置的 useState Hook。它允许您将组件状态存储在一个变量中,并根据需要进行管理。在本教程中,我们将编写一个扩展 useState Hook 的 Hook,以实现撤销/重做功能。
代码
我们先来看代码,然后我在下面进行解释。
import { useMemo, useState } from "react";
// If you're only working with primitives, this is not required
import isEqual from "lodash/isEqual";
export default function useUndoableState(init) {
const [states, setStates] = useState([init]); // Used to store history of all states
const [index, setIndex] = useState(0); // Index of current state within `states`
const state = useMemo(() => states[index], [states, index]); // Current state
const setState = (value) => {
// Use lodash isEqual to check for deep equality
// If state has not changed, return to avoid triggering a re-render
if (isEqual(state, value)) {
return;
}
const copy = states.slice(0, index + 1); // This removes all future (redo) states after current index
copy.push(value);
setStates(copy);
setIndex(copy.length - 1);
};
// Clear all state history
const resetState = (init) => {
setIndex(0);
setStates([init]);
};
// Allows you to go back (undo) N steps
const goBack = (steps = 1) => {
setIndex(Math.max(0, Number(index) - (Number(steps) || 1)));
};
// Allows you to go forward (redo) N steps
const goForward = (steps = 1) => {
setIndex(Math.min(states.length - 1, Number(index) + (Number(steps) || 1)));
};
return {
state,
setState,
resetState,
index,
lastIndex: states.length - 1,
goBack,
goForward,
};
}
概念
与 useState 类似,useUndoableState 也只接受一个参数,即初始值。在底层,该钩子函数使用两个主要变量来确定状态—— index(数字)和states(数组)。(数字)states存储状态的历史值,而(数组)则index通过指示当前在数组中的位置来确定当前状态。
goBack您可以使用钩子发出的 ` and`函数来遍历历史状态goForward。但是,如果您调用 ` setStateand`函数时当前状态index不在数组末尾states,则该状态之后的所有状态都index将被清除,index您将返回到数组末尾states。换句话说,一旦调用 `and` 函数setState,您就无法再进行重做操作。
下表试图对钩子返回的对象提供更详细的解释:
| 支柱 | 类型 | 用法 | 描述 |
|---|---|---|---|
| 状态 | any |
当前状态,已通过传递的参数初始化。 | |
| 设置状态 | func |
setState(value) |
将状态设置为value. 当前值之后的所有值都index将被清除。 |
| 重置状态 | func |
resetState(value) |
删除历史状态并重置为值 |
| 指数 | number |
states数组中的当前索引 |
|
| lastIndex | number |
数组中的最后一个索引states。可用于确定是否可以goForward。canGoForward = index < lastIndex |
|
| 返回 | func |
goBack(2) |
回溯已走过的步数 |
| 前进 | func |
goForward(3) |
向前推进已走过的步数 |
用法
import React from "react";
import useUndoableState from "path/to/hook";
const init = { text: "The quick brown fox jumps over the lazy dog" };
export default function Document() {
const {
state: doc,
setState: setDoc,
resetState: resetDoc,
index: docStateIndex,
lastIndex: docStateLastIndex,
goBack: undoDoc,
goForward: redoDoc
} = useUndoableState(init);
const canUndo = docStateIndex > 0;
const canRedo = docStateIndex < docStateLastIndex;
return (
<div style={{ display: "block" }}>
<textarea
style={{ margin: "16px" }}
onChange={(event) => setDoc({ text: event.target.value })}
rows="5"
value={doc.text}
/>
<div>
<button
onClick={() => undoDoc()}
disabled={!canUndo}
style={{ marginRight: "8px" }}
>
Undo
</button>
<button
onClick={() => redoDoc()}
disabled={!canRedo}
style={{ marginRight: "8px" }}
>
Redo
</button>
<button onClick={() => resetDoc(init)}>Reset</button>
</div>
</div>
);
}
总结发言
FormBlob具备撤销/重做功能,是少数几款无需编写代码即可创建表单的工具之一,让您可以灵活地编辑表单,而无需担心丢失之前的状态。作为一款无需代码的工具,FormBlob 让任何人都能在 2 分钟内创建并发布精美的表单和调查问卷。立即免费试用!
文章来源:https://dev.to/jeremyling/react-hook-to-allow-undoredo-4poj