我有以下组件:
import React, { useEffect, useState } from 'react';
const UndoInput = () => {
const [name, setName] = useState('');
return(
<div className = 'Name'>
<input
onChange = {(e) => setName(e.target.value)}
value = {name}>
</input>
<Undo
name = {name}
setName = {setName}
/>
</div>
);
}
export default UndoInput;
<Undo/>
组件是一个缓冲区,用于存储每次更改时输入的值,允许执行撤消操作。
const Undo = ({name, setName}) => {
const [buffer, setBuffer] = useState(['', '', '']);
const [index, setIndex] = useState(-1);
useEffect(() => {
let copy = [...buffer, name].slice(-3);
let pos = index + 1;
if(pos > 2)
pos = 2;
setBuffer(copy);
setIndex(pos);
}, [name]);
const undo = () => {
let pos = index - 1;
if(pos < 0)
pos = -1;
setName(buffer[pos]);
setIndex(pos);
}
return(
<button onClick = {undo}>Undo</button>
);
}
示例:
如果用户在输入中键入“abc”,则为 buffer = ['a', 'ab', 'abc']
和 index = 2
。
当用户点击按钮时,组件应该回到之前的状态。这意味着,buffer = ['a', 'ab', 'abc']
和 index = 1
。
点击按钮后,执行 setName(buffer[pos])
,重新触发 useEffect()
:buffer = ['ab', 'abc', 'ab']
和 index = 2
(不需要)。
如果 useEffect()
函数重新触发了 undo()
钩子,我如何防止重新触发它?
答案 0 :(得分:3)
我不会用useEffect此。在我看来,您的撤消缓冲区不是 React DOM 意义上的副作用,而是您想要应用于状态更改的内部逻辑。同样,您的 Undo 组件也是一个按钮,它碰巧也有关于历史和状态的可重用逻辑,可以说这些逻辑更多地属于父级而不是按钮本身。
总的来说,我认为这种方法不是一个干净的自上而下的流程,并且往往会导致 React 中出现此类问题。
我会先将您的撤消逻辑和状态放在父级中:
const handleChange = function(e){
// add to undo buffer here
setName(e.target.value);
}
const handleUndo = function(){
// fetch your undo buffer here
setName(undoValue);
}
onChange = {handleChange}
onClick = {handleUndo}
在连接完所有内容后,您可能会觉得这样做会失去撤消组件的干净可重用性,而且您是对的。但这正是 Custom Hooks 的用途:可重用的有状态逻辑。有很多方法可以实现,但这里有一个您可以编写的自定义钩子示例:
const [name, setName, undoName] = useUndoableState('');
钩子将替换 useState(并在钩子内使用它本身),管理缓冲区,并提供一个额外的函数来调用撤销并设置新名称。
<input onChange={setName} />
<button onClick={undoName}>Undo</button>