首页 > 解决方案 > 从反应钩子返回回调的最佳方法

问题描述

我想制作一个自定义挂钩来响应某些元素的单击事件并进行一些状态更新:

function useCustom() {
  // a sample state within the hook
  const [counter, setCounter] = useState(0);

  // the callback I'm talking about
  const onClick = event => {
    console.log(`Hey! counter is ${counter}`);
    setCounter(val=>val+1);
  }

  // returning the callback to be used in the component
  return {onClick};
}

现在使用这个钩子的组件

function MyComp(props) {
  const cbs = useCustom();

  // component onclick handler
  const onClick = event => cbs.onClick(event);

  return <button onClick={onClick}>Click me!</button>
}

这种方法存在一些问题。首先,钩子中的 callabck 函数 onClick 以某种方式连接到该钩子的第一个闭包,这意味着其中的 counter 的值始终为 0(它在每次单击时都会记录“嘿!计数器为 0”)。好吧,为了解决这个问题,我需要使用 useRef ...我知道该怎么做,但我主要关心的是这个回调以某种方式连接到钩子的早期(和过时)闭包,所以它保持这个闭包(及其所有变量)活着(不是垃圾收集),因此它是一种内存泄漏(??我在这里犯错了吗) - 所以一般来说,从钩???

一个看起来更反应的替代解决方案是使用状态变量而不是钩子内的回调并返回该状态的设置状态函数:

function useCustom() {
  // a sample state within the hook
  const [counter, setCounter] = useState(0);
  
  // state to hold the last click event
  const [clickEvent, setClickEvent] = useState(null);

  // replacing the callback with an effect callback
  useEffect(() => {
    console.log(`Hey! counter is ${counter}`);
    setCounter(val=>val+1);
  }, [clickEvent]);

  // returning the set state function instead of callback
  return {setClickEvent};
}

然后在组件中:

function MyComp(props) {
  const cbs = useCustom();

  // component onclick handler
  const onClick = event => cbs.setClickEvent(event);

  return <button onClick={onClick}>Click me!</button>
}

请注意,这样做的不利之处在于,我们将在单击时对钩子进行两次渲染(即执行)(一个是由于 setClickEvent 而下一个是由于 setCounter 在 clickEvent 的效果内...)。与回调方法相比,这可能是它的权衡。

那么谁赢了呢?回调方法或 setState 方法,或...??

标签: javascriptreactjscallbackreact-hooks

解决方案


setState是异步的。你可以在 React 的这个文档中看到:

您可以通过添加一个将在计数器更改时触发的值来记录该useEffectcounter

 React.useEffect(() => {
    console.log(`Hey! counter is ${counter}`); // <= log when counter changes
  }, [counter]);

工作示例:

function useCustom() {
  // a sample state within the hook
  const [counter, setCounter] = React.useState(0);

  // the callback I'm talking about
  const onClick = (event) => {
    setCounter((val) => val + 1);
  };

  React.useEffect(() => {
    console.log(`Hey! counter is ${counter}`);
  }, [counter]);

  // returning the callback to be used in the component
  return { onClick };
}
function App() {
  const cbs = useCustom();
  // component onclick handler
  const onClick = (event) => cbs.onClick(event);

  return <button onClick={onClick}>Click me!</button>;
}

ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>


推荐阅读