
Содержание статьи
от Чарли Ли

Привязка обработчиков событий в 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()
позвонили впервые!

Как на диаграмме выше, когда 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
поэтому <butt
on> не будет повторно воспроизведен, пока кнопка не изменится.

Связывание с помощью функции стрелки
С помощью свойств класса 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
никогда больше не изменится. Таким образом, это гарантирует <butt
on> не будет повторно воспроизведен. Этот подход, пожалуй, самый лучший способ сделать привязки. Он прост, легко читается, а главное он работает.
Динамическое связывание с функцией стрелки для нескольких элементов
Используя тот же трюк с функцией стрелки, мы можем использовать тот же обработчик для нескольких вводов:
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()
называется оба<inp
ut> будет повторно воспроизведен.
В самом деле, я считаю этот подход разумным, и я не хочу писать несколько handleXXXChange
для каждого поля. К счастью, этот тип многофункционального обработчика реже появится в списке. Это значит, что их будет только пара <inp
ut> повторно отображаемые компоненты и, вероятно, не возникнет проблем с производительностью.
В любом случае, преимущества, которые он приносит нам, гораздо больше, чем потеря производительности. Поэтому я предлагаю вам воспользоваться этим подходом напрямую.
Если эти проблемы с производительностью становятся значительными, я бы предложил кэшировать обработчики во время выполнения привязок (но это сделает код менее читаемым):
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 rende
r() функцияn, this.handleCl
ick можно использовать напрямую.