简体   繁体   English

如何正确使用 React `useCallback` 的依赖列表?

[英]How to correctly use React `useCallback`'s dependencies list?

I have an example like this:我有一个这样的例子:

codesandebox 码箱

I want to modify a state value in a callback, then use the new state value to modify another state.我想在回调中修改一个 state 值,然后使用新的 state 值修改另一个 state。

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("0");
  const [added, setAdded] = useState(false);

  const aNotWorkingHandler = useCallback(
    e => {
      console.log("clicked");
      setCount(a => ++a);
      setText(count.toString());
    },
    [count, setCount, setText]
  );

  const btnRef = useRef(null);
  useEffect(() => {
    if (!added && btnRef.current) {
      btnRef.current.addEventListener("click", aNotWorkingHandler);
      setAdded(true);
    }
  }, [added, aNotWorkingHandler]);

return <button ref={btnRef}> + 1 </button>

However, after this handler got called, count has been successfully increased, but text hasn't.但是,在调用此处理程序后, count已成功增加,但text没有。

Can you guys help me to understand why this happened?你们能帮我理解为什么会这样吗? and how to avoid it cleanly?以及如何彻底避免它?

Thank you!谢谢!

If count and state are always supposed to be in lockstep, just with one being a number and one being a string, then i think it's a mistake to have two state variables.如果countstate总是应该保持同步,只是一个是数字,一个是字符串,那么我认为有两个 state 变量是错误的。 Instead, just have one, and derive the other value from it:相反,只拥有一个,并从中得出另一个值:

const [count, setCount] = useState(0);
const text = "" + count;
const [added, setAdded] = useState(false);

const aNotWorkingHandler = useCallback(
  e => {
    setCount(a => ++a);
  },
  []
);

In the above useCallback, i have an empty dependency array.在上面的 useCallback 中,我有一个空的依赖项数组。 This is because the only thing that's being used in the callback is setCount.这是因为回调中唯一使用的是 setCount。 React guarantees that state setters have stable references, so it's impossible for setCount to change, and thus no need to list it as a dependency. React 保证 state setter 具有稳定的引用,因此 setCount 不可能更改,因此无需将其列为依赖项。

There are few things causing the issue.导致问题的原因很少。

Setter does not update the count value immediately. Setter 不会立即更新count数值。 Instead it "schedules" the component to re-render with the new count value returned from the useState hook.相反,它“安排”组件使用从 useState 挂钩返回的新count数值重新呈现。 When the setText setter is called, the count is not updated yet, because the component didn't have chance to re-render in the mean time.当调用setText setter 时, count还没有更新,因为组件没有机会重新渲染。 It will happen some time after the handler is finished.它会在处理程序完成后的某个时间发生。

setCount(a => ++a);        // <-- this updates the count after re-render
setText(count.toString()); // <-- count is not incremented here yet

You are calling addEventListener only once and it remembers the first value of count .您只调用addEventListener一次,它会记住count的第一个值。 It is good you have aNotWorkingHandler in the dependencies - the onEffect is being re-run when new count and thus new handler function comes.最好在依赖项中有一个aNotWorkingHandler - onEffect在新count时重新运行,因此新的处理程序 function 出现。 But your added flag prevents the addEventListener from being called then.但是您added的标志会阻止addEventListener被调用。 The button stores only the first version of the handler function. The one with count === 0 closured in it.该按钮仅存储处理程序 function 的第一个版本。count count === 0的那个在其中关闭。

useEffect(() => {
  if (!added && btnRef.current) { // <-- this prevents the event handler from being updated
    btnRef.current.addEventListener("click", aNotWorkingHandler); // <-- this is called only once with the first instance of aNotWorkingHandler
    setAdded(true);
  } else {
    console.log("New event handler arrived, but we ignored it.");
  }
}, [added, aNotWorkingHandler]); // <-- this correctly causes the effect to re-run when the callback changes

Just removing the added flag would, of course, cause all the handlers to pile up.当然,仅仅删除added的标志会导致所有处理程序堆积起来。 Instead, just use onClick which correctly adds and removes the event handler for you.相反,只需使用onClick正确地为您添加和删除事件处理程序。

<button onClick={aNotWorkingHandler} />

In order to update a value based on another value, I'd probably use something like this (but it smells of infinite loop to me):为了根据另一个值更新一个值,我可能会使用这样的东西(但它对我来说有无限循环的味道):

useEffect(
  () => {
    setText(count.toString());
  },
  [count]
);

Or compute the value first, then update the states:或者先计算值,然后更新状态:

  const aNotWorkingHandler = useCallback(
    (e) => {
      const newCount = count + 1;
      setCount(newCount);
      setText(newCount.toString());
    },
    [count]
  );

I agree with @nicholas-tower, that if the other value does not need to be explicitly set and is always computed from the first one, it should be just computed as the component re-renders.我同意 @nicholas-tower 的观点,如果不需要显式设置另一个值并且总是从第一个值计算,那么它应该只在组件重新渲染时计算。 I think his answer is correct, hope this context answers it for other people getting here.我认为他的回答是正确的,希望这个上下文能为到达这里的其他人解答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM