Лучший способ привязать обработчики событий в React

1656622813 luchshij sposob privyazat obrabotchiki sobytij v react

от Чарли Ли

mfAwHg-pm-FWwzG58t-i6Zd4XUNjp3J-sPcD
Изображение: Flickr от Лиз Вест

Привязка обработчиков событий в React может быть сложной (за это вам нужно поблагодарить JavaScript). Для тех, кто знает историю Perl и Python, TMTOWTDI (имеется более чем один способ сделать это) и TOOWTDI (есть только один способ сделать это) должны быть знакомыми словами. К сожалению, по крайней мере для связывания событий, JavaScript является языком TMTOWTDI, который всегда вводит разработчиков в замешательство.

В этой публикации мы рассмотрим распространенные способы создания привязок событий в React, и я покажу вам их плюсы и минусы. И главное, я помогу вам найти «Только один путь» или, по крайней мере, мой любимый.

В этой публикации предполагается, что вы понимаете необходимость привязки, например, почему нам это нужно this.handler.bind(this)или разница между function() { console.log(this); } и () => { console.log(this); }. Если вы запутаетесь в этих вопросах, Саураб Миссра совершил удивительный пост, который их объясняет.

Динамическая привязка в render()

Первый случай, который обычно используется — это вызов .bind(this) в render() функция. Например:

class HelloWorld extends Component {  handleClick(event) {}  render() {    return (      <p>Hello, {this.state.name}!</p>      <button onClick={this.handleClick.bind(this)}>Click</button>    );  }}

Конечно, это сработает. Но подумайте об одном: что произойдет, если this.state.nameизменения?

Можно сказать, что меняется this.state.name приведет к повторномуrender() . хорошо. Компонент отображается для обновления части имени. Но будет ли воспроизведена кнопка?

Учтите тот факт, что React использует виртуальную DOM. Когда происходит визуализация, он сравнивает обновленную виртуальную DOM с предыдущей виртуальной DOM, а затем только обновляет измененные элементы к фактическому дереву DOM.

В нашем случае, когда render() это называется, this.handleClick.bind(this) также будет вызвано для привязки обработчика. Этот вызов создаст a совершенно новый обработчиккоторый полностью отличается от обработчика, используемого при render() позвонили впервые!

u0GzKJdrXoomFQqomNjOV3UecQV8tHrTj9UM
Виртуальный DOM для динамической связки. Элементы синего цвета будут повторно воспроизведены.

Как на диаграмме выше, когда render() назывался раньше, this.handleClick.bind(this) вернулся funcA чтобы React знал onChange был funcA.

Позже, когда render() называется снова, this.handleClick.bind(this) вернулся funcB (Обратите внимание, что он возвращает новую функцию при каждом вызове). Таким образом, React это знает onChange больше не funcAчто означает, что button необходимо повторно отобразить.

Одна кнопка может не быть проблемой. Но что если у вас есть 100 кнопок, представленных в списке?

render() {  return (    {this.state.buttons.map(btn => (      <button key={btn.id} onChange={this.handleClick.bind(this)}>        {btn.label}      </button>    ))}  );}

В приведенном выше примере любое изменение метки кнопки приведет к тому, что все кнопки будут повторно воспроизведены, поскольку все кнопки создадут новую onChange обработчик.

Связать в конструкторе()

Старый школьный способ состоит в том, чтобы выполнить привязку в конструкторе. Ничего особенного:

class HelloWorld extends Component {  constructor() {    this.handleClick = this.handleClickFunc.bind(this);  }  render() {    return (<button onClick={this.handleClick}/>);  }}

Этот способ гораздо лучше предыдущего. Звонок render() не создаст новый обработчик для onClickпоэтому <button> не будет повторно воспроизведен, пока кнопка не изменится.

iTs98nwnYdvxE2fFoO5N1Rzcg-u-cD7LL9g1
Виртуальный DOM для привязки конструктора. Элементы синего цвета будут повторно воспроизведены.

Связывание с помощью функции стрелки

С помощью свойств класса ES7 (сейчас поддерживается Babel), мы можем выполнять привязки в определении метода:

class HelloWorld extends Component {  handleClick = (event) => {    console.log(this.state.name);  }  render() {    return (<button onClick={this.handleClick}/>)  }}

В приведенном выше коде handleClick это задача, эквивалентная:

constructor() {  this.handleClick = (event) => { ... };}

Итак, после инициализации компонента this.handleClick никогда больше не изменится. Таким образом, это гарантирует <button> не будет повторно воспроизведен. Этот подход, пожалуй, самый лучший способ сделать привязки. Он прост, легко читается, а главное он работает.

Динамическое связывание с функцией стрелки для нескольких элементов

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

class HelloWorld extends Component {  handleChange = name => event => {    this.setState({ [name]: event.target.value });  }  render() {    return (      <input onChange={this.handleChange('name')}/>      <input onChange={this.handleChange('description')}/>    )  }}

На первый взгляд это выглядит удивительно благодаря своей простоте. Однако если вы внимательно подумаете, вы увидите, что он имеет ту же проблему, что и первый подход: каждый раз render() называется оба<input> будет повторно воспроизведен.

В самом деле, я считаю этот подход разумным, и я не хочу писать несколько handleXXXChange для каждого поля. К счастью, этот тип многофункционального обработчика реже появится в списке. Это значит, что их будет только пара <input> повторно отображаемые компоненты и, вероятно, не возникнет проблем с производительностью.

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

Если эти проблемы с производительностью становятся значительными, я бы предложил кэшировать обработчики во время выполнения привязок (но это сделает код менее читаемым):

class HelloWorld extends Component {  handleChange = name => {    if (!this.handlers[name]) {      this.handlers[name] = event => {        this.setState({ [name]: event.target.value });      };    }    return this.handlers[name];    }   render() {    return (      <input onChange={this.handleChange('name')}/>      <input onChange={this.handleChange('description')}/>    )  }}

Вывод

Следуя привязкам событий в React, мы должны очень внимательно проверить, генерируются ли обработчики динамически. Обычно это не проблема, когда пораженные компоненты появляются всего один-два раза. Но когда обработчики событий появляются в списке, это может привести к серьезным проблемам с производительностью.

Решение

  • Используйте привязывание функции стрелки, когда это возможно
  • Если вы должны генерировать привязки динамически, подумайте о кэшировании обработчиков, если привязки становятся проблемой производительности

Спасибо, что прочли! Надеюсь, этот пост был полезен. Если вы считаете эту публикацию полезной, поделитесь ею с другими людьми, рекомендуя ее.

Обновление:

Вспомнили Омри Лусон и Шеш lodash-decorators и react-autobind пакеты для более удобного скрепления. Лично я не являюсь большим поклонником автоматического выполнения любых задач (я всегда стараюсь, чтобы такие привязки были минимальными), но автоматическая привязка — это совершенно отличный способ написать чистый код и сэкономить больше усилий. Код будет следующим:

import autoBind from 'react-autobind';class HelloWorld() {  constructor() {    autoBind(this);  }
  handleClick() {    ...  }  render() {    return (<button onClick={this.handleClick}/>);  }}

Так как autoBind обрабатывать привязки автоматически, не нужно использовать трюк с функцией стрелки ( handleClick = () => {} ), чтобы выполнить привязку, а в the render() функцияn, this.handleClick можно использовать напрямую.

Добавить комментарий

Ваш адрес email не будет опубликован.