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

Luc*_*emy 21 javascript jsx reactjs react-hooks

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

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

  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,当我使用基于钩子的功能范例时,发现有两个选择:

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

  return <button onClick={handleClick}>Click Me</button>;
};
Run Code Online (Sandbox Code Playgroud)

或者:

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

  return <button onClick={handleClick.current}>Click Me</button>;
};
Run Code Online (Sandbox Code Playgroud)

客观上哪个更好,出于什么原因?还有我还没有听说过的(更好)方法吗?

谢谢您的帮助。

编辑:我在CodeSandbox上放了一个示例显示了这两种方法。正如您从那里的代码可以看到的那样,似乎都没有必要在每个渲染器上重新创建事件处理程序,因此,我认为不可能出现性能问题。

Ret*_*sam 30

我不会推荐任何useStateuseRef

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

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

  return <button onClick={handleClick}>Click Me</button>;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

但是第二点有时是正确的:如果对某个组件进行了优化(例如,使用React.memo或通过将其定义为PureComponent),以使其仅在提供新道具时才重新渲染,则传递新的函数实例可能会导致该组件不必要地重新渲染。

为了解决这个问题,React提供了useCallback用于记住回调的钩子:

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

    return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};
Run Code Online (Sandbox Code Playgroud)

useCallback仅在必要时(每当deps数组中的值更改时)返回一个新函数,因此OptimizedButtonComponent不会重新渲染超出必要的内容。因此,这解决了问题2。(请注意,它无法解决问题#1,每次渲染时,仍会创建一个新函数并将其传递给useCallback

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

  • “由于&lt;button&gt;不是经过优化的组件,因此您带有&lt;button&gt;的原始示例将不会从经过记忆的回调中受益。”这是什么意思?为什么没有帮助?DOM的“ onClick”道具不会在每个渲染器上盲目更新,这不是我们想要的吗? (3认同)