在 React 中使用钩子创建事件处理程序的正确方法?

Correct way to create event handlers using hooks in React?

在典型的基于 class 的 React 组件中,这就是我创建事件处理程序的方式:

class MyComponent extends Component {
  handleClick = () => {
    ...
  }

  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

但是,当我使用基于钩子的函数范式时,我发现自己有两个选择:

const MyComponent = () => {
  const [handleClick] = useState(() => () => {
    ...
  });

  return <button onClick={handleClick}>Click Me</button>;
};

或者:

const MyComponent = () => {
  const handleClick = useRef(() => {
    ...
  });

  return <button onClick={handleClick.current}>Click Me</button>;
};

哪一个客观上更好,原因是什么?还有其他(更好的)方法我还没有听说过也没有发现吗?

感谢您的帮助。

编辑:我举了一个例子 here on CodeSandbox 来展示这两种方法。两者似乎都没有在每次渲染时不必要地重新创建事件处理程序,正如您从那里的代码中看到的那样,所以我认为可能的性能问题是不可能的。

我不推荐 useStateuseRef

你实际上根本不需要任何钩子。在许多情况下,我建议简单地这样做:

const MyComponent = () => {
  const handleClick = (e) => {
    //...
  }

  return <button onClick={handleClick}>Click Me</button>;
};

但是,有时建议避免在渲染函数内声明函数(例如 jsx-no-lambda tslint 规则)。这有两个原因:

  1. 作为性能优化,避免声明不必要的函数。
  2. 避免不必要的纯组件重新渲染。

我不太担心第一点:钩子将在函数内部声明函数,而且该成本不太可能成为影响应用程序性能的主要因素。

但第二点有时是有效的:如果一个组件被优化(例如使用 React.memo 或被定义为 PureComponent)以便它只在提供新道具时重新渲染,传递一个新的函数实例可能会导致组件不必要地重新渲染。

为了处理这个问题,React 提供了 useCallback 钩子,用于记忆回调:

const MyComponent = () => {
    const handleClick = useCallback((e) => {
        //...
    }, [/* deps */])

    return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};

useCallback 只会在必要时 return 一个新函数(每当 deps 数组中的值发生变化时),因此 OptimizedButtonComponent 不会重新渲染超过必要的次数。所以这解决了问题#2。 (请注意,它没有解决问题 #1,每次我们渲染时,仍然会创建一个新函数并传递给 useCallback

但我只会在必要时这样做。您可以将每个回调包装在 useCallback 中,它会起作用......但在大多数情况下,它无济于事:您使用 <button> 的原始示例不会从记忆回调中受益,因为<button> 不是优化组件。