Обработка событий в React: onClick срабатывает при рендере

Пройдите тест, узнайте какой профессии подходите и получите бесплатную карьерную консультацию
В конце подарим скидку до 55% на обучение
Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для предотвращения автоматического выполнения функции во время рендеринга, убедитесь, что в onClick вы передаёте ссылку на функцию, а не вызываете саму функцию. В случае инлайн обработки воспользуйтесь стрелочной функцией, либо передайте ссылку на обработчик напрямую, если входные параметры отсутствуют.

jsx
Скопировать код
// Неправильно: функция будет выполняться сразу после рендеринга!
<button onClick={ringTheBell()} />

// Правильно: функция ожидает клика.
<button onClick={() => ringTheBell()} />

// Если параметры не требуются: оптимальное решение.
<button onClick={ringTheBell} />

Прочитав статью до конца, вы узнаете больше о важных нюансах различия между ссылками на функции и вызовами функций.

Обработка параметров в onClick

Если нужно передать параметры в onClick, используйте стрелочную функцию или метод .bind. Выбор за вами, но не забывайте о производительности:

Стрелочная функция:

jsx
Скопировать код
<button onClick={() => tossTheRing(intoTheVolcano)} />

Привязанная функция:

jsx
Скопировать код
<button onClick={tossTheRing.bind(this, intoTheVolcano)} />

Обратите внимание: .bind создаёт новый экземпляр функции каждый раз при рендеринге. Стрелочная функция в этом случае является элегантной альтернативой.

Производительность и предотвращение возможных ошибок

Для избежания ненужных ререндеров определите callback-функции вне метода render в классовых компонентах или вне тела функционального компонента. Вот пример хорошего паттерна с использованием хуков для сохранения стабильных ссылок на функции:

jsx
Скопировать код
function Task({taskId, taskName}) {
  // Преимущества 'useCallback' очевидны!
  const removeTask = useCallback(() => {
    dontForgetToDelete(taskId);
  }, [taskId]);

  return <button onClick={removeTask}>Удалить {taskName}</button>;
}

Избегайте следующих распространённых ошибок:

  • Определение функции внутри метода render без useCallback, что приводит к созданию новой функции при каждом рендере.
  • Использование onClick={makeCoffee()} вместо onClick={() => makeCoffee()}, что приводит к нежелательному поведению при рендере.

Визуализация

Событие onClick можно представить как звонок в дверь (🏠):

jsx
Скопировать код
// Обычный React компонент с кнопкой
function WelcomeGuest() {
  // Приглашаем войти!
  function ringBell() {
    alert('Динь-дон! Добро пожаловать!');
  }

  return (
    <button onClick={ringBell}>Звонок</button>
  );
}

Сценарий:

Markdown
Скопировать код
🏠: [Дверь закрыта, звонок не звенит]

При корректном использовании обработчика onClick:

Markdown
Скопировать код
Звонок (👆): [Дверь остаётся закрытой, звонок звенит только после нажатия]

Но в случае прямого вызова функции:

jsx
Скопировать код
<button onClick={ringBell()}>Звонок</button>

Будет:

Markdown
Скопировать код
Ошибка в настройке: [Звонок начинает серию звуков 🛎️🛎️🛎️, гости в замешательстве 😕]

Контролируйте динь-дон, пердавая в onClick ссылку на функцию, вместо её вызова.

Управление this в классовых компонентах

В классовых компонентах важно правильно управлять контекстом переменной this. Вот несколько способов, как это можно сделать:

Классовое свойство + стрелочная функция:

jsx
Скопировать код
class MyButton extends React.Component {
  handleClick = () => {
    console.log('Мир стало лучше!');
  };

  render() {
    return <button onClick={this.handleClick}>Нажми на меня</button>;
  }
}

Привязка в конструкторе:

jsx
Скопировать код
class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('Мир стал лучше, как в старые добрые времена.');
  }

  render() {
    return <button onClick={this.handleClick}>Нажми на меня</button>;
  }
}

Оба варианта эффективно управляют this, однако стрелочные функции выглядят более современно и позволяют избежать проблем с производительностью, связанными с необходимостью повторного привязывания функций при каждом рендере.

Пишите достойный и понятный код

Ставьте перед собой цель написать понятный и качественный код:

  • Выносите сложные обработчики событий из JSX-выражений, чтобы не перегружать render метод.
  • Применяйте хуки и вспомогательные функции, если логика повторяется между разными обработчиками и компонентами.
  • Оформляйте обработчики событий так, чтобы они были доступны, добавляя семантически правильные aria- атрибуты к интерактивным элементам.

Полезные материалы

  1. Handling Events – React — Основы работы с событиями в React.
  2. EventTarget: addEventListener() method – Web APIs | MDN — Основы работы с событиями в JavaScript.
  3. Arrow Functions: To bind or not to bind? – Medium — Преимущества использования стрелочных функций и this.
  4. React Patterns — Паттерны проектирования в React.
  5. Binding vs Arrow-function on StackOverflow — Обсуждение .bind и стрелочных функций.
  6. JS Event Delegation — Дополнительная информация о делегировании событий.
  7. RWieruch — Распространённые сложности при работе с JavaScript в контексте React.