从特定函数更新状态时防止 useEffect 重新触发

时间:2021-04-08 09:23:02

标签: reactjs react-hooks

我有以下组件:

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() 钩子,我如何防止重新触发它?

1 个答案:

答案 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>