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

问题描述 投票:7回答:1

在典型的基于类的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显示两种方法。似乎不必在每个渲染上不必要地重新创建事件处理程序,正如您可以从那里的代码中看到的那样,因此我认为可能的性能问题是不可能的。

javascript reactjs jsx react-hooks
1个回答
13
投票

我不会推荐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只会在必要时返回一个新函数(每当deps数组中的值发生变化时),因此OptimizedButtonComponent将不会重新渲染超过必要的值。所以这解决了问题#2。 (请注意,它没有解决问题#1,每次渲染时,仍会创建一个新函数并传递给useCallback

但我只会在必要时这样做。您可以在useCallback中包装每个回调,它会起作用......但在大多数情况下,它没有任何帮助:<button>的原始示例不会受益于memoized回调,因为<button>不是优化组件。

© www.soinside.com 2019 - 2024. All rights reserved.