Правильный способ создания обработчиков событий с использованием хуков в React? - PullRequest
11 голосов
/ 05 марта 2019

В типичном компоненте 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>;
};

Какой объективно лучше и по какой причине? Есть ли другой (лучший) способ, о котором я еще не слышал и не обнаружил?

Спасибо за вашу помощь.

Редактировать: я поместил пример здесь на CodeSandbox , показывающий оба метода. Похоже, что ни один из них не создает излишнего пересмотра обработчика событий для каждого рендера, как вы можете видеть из приведенного там кода, поэтому я думаю, что о возможной проблеме производительности не может быть и речи.

1 Ответ

14 голосов
/ 06 марта 2019

Я бы не рекомендовал useState или useRef.

Тебе здесь вообще не нужен крючок.Во многих случаях я бы рекомендовал просто сделать это:

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> не извлечет выгоду из запомненного обратного вызова, так как <button> isnоптимизированный компонент

...