如何防止内联函数绑定到旧状态值

IT技术 javascript reactjs react-hooks
2021-05-03 22:19:23

在使用钩子的 React 组件的项目中,我试图了解如何正确避免调用绑定到旧状态值的回调。下面的例子说明了这个问题(但不是我正在处理的代码)。

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Message = () => {
  const [message, setMessage] = useState("");

  function doStuff() {
    console.log(message);
  }

  useEffect(() => {
    setInterval(doStuff, 1000)
  }, []);

  return (
    <div>
      <input
        type="text"
        value={message}
        placeholder="Enter a message"
        onChange={e => setMessage(e.target.value)}
      />
      <p>
        <strong>{message}</strong>
      </p>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Message />, rootElement);

这里的问题当然是setIntervaldoStuff函数保持在第一次(也是唯一一次)调用效果时的状态。那时message状态是空的,因此间隔函数将每秒打印一个空字符串,而不是实际在文本框中的消息。

在我的实际代码中,我有外部事件应该触发组件内部的函数调用,并且它们遇到了同样的问题。

我该怎么办?

2个回答

您应该useCallback并将其作为依赖项传递给您的效果。

const doStuff = useCallback(() => {
  console.log(message);
}, [message]);

useEffect(() => {
  const interval = setInterval(doStuff, 1000);
  return () => clearInterval(interval); // clean up
}, [doStuff]);

在这里,当message更新时,它将在doStuff

你也可以把它放在它自己的钩子里。我的生产代码中有这个

/**
 * A react hook for setting up an interval
 * @param handler - Function to execute on interval
 * @param interval - interval in milliseconds
 * @param runImmediate - If the function is executed immediately
 */
export const useInterval = (handler: THandlerFn, interval: number | null, runImmediate = false): void => {
  const callbackFn = useRef<THandlerFn>()

  // Update callback function
  useEffect((): void => {
    callbackFn.current = handler
  }, [handler])

  // Setup interval
  useEffect((): (() => void) | void => {
    const tick = (): void => {
      callbackFn.current && callbackFn.current()
    }

    let timerId: number
    if (interval) {
      if (runImmediate) {
        setTimeout(tick, 0)
      }

      timerId = setInterval(tick, interval)

      return (): void => {
        clearInterval(timerId)
      }
    }
  }, [interval, runImmediate])
}