2

我有一个这样的例子:

密码箱

我想在回调中修改一个状态值,然后使用新的状态值来修改另一个状态。

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>

但是,在调用此处理程序后,count已成功增加,但text没有。

你们能帮我理解为什么会这样吗?以及如何干净地避免它?

谢谢!

4

2 回答 2

2

如果count并且state总是应该步调一致,只有一个是数字,一个是字符串,那么我认为拥有两个状态变量是错误的。相反,只需拥有一个,并从中得出另一个值:

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

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

在上面的 useCallback 中,我有一个空的依赖数组。这是因为回调中唯一使用的是 setCount。React 保证状态设置器具有稳定的引用,因此 setCount 不可能更改,因此无需将其列为依赖项。

于 2020-04-14T18:46:32.947 回答
1

导致问题的原因很少。

Setter 不会count立即更新值。相反,它“安排”组件使用count从 useState 挂钩返回的新值重新渲染。setText调用 setter时,count尚未更新,因为该组件同时没有机会重新渲染。它会在处理程序完成后的一段时间内发生。

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

您只调用addEventListener一次,它会记住 的第一个值countaNotWorkingHandler在依赖项中有很好的 -当新的并且因此新的处理程序函数出现onEffect时正在重新运行。count但是你的added标志阻止了addEventListener被调用。该按钮仅存储处理函数的第一个版本。里面有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

当然,仅仅移除added标志就会导致所有处理程序堆积起来。相反,只需使用onClickwhich 为您正确添加和删除事件处理程序。

<button onClick={aNotWorkingHandler} />

为了根据另一个值更新一个值,我可能会使用这样的东西(但它对我来说是无限循环的味道):

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

或者先计算值,然后更新状态:

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

我同意@nicholas-tower 的观点,如果另一个值不需要显式设置并且总是从第一个值计算,它应该只是在组件重新渲染时计算。我认为他的回答是正确的,希望这个上下文可以为其他人提供答案。

于 2021-05-13T18:49:59.430 回答