Почему реквизиты JSX не должны использовать функции стрелки или связывать?
Я запускаю lint с моим приложением React и получаю эту ошибку:
error JSX props should not use arrow functions react/jsx-no-bind
И здесь я запускаю функцию стрелки (внутри onClick
):
{this.state.photos.map(tile => (
<span key={tile.img}>
<Checkbox
defaultChecked={tile.checked}
onCheck={() => this.selectPicture(tile)}
style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
/>
<GridTile
title={tile.title}
subtitle={<span>by <b>{tile.author}</b></span>}
actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
>
<img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
</GridTile>
</span>
))}
Это плохая практика, которую следует избегать? И какой лучший способ сделать это?
Ответы
Ответ 1
Почему вы не должны использовать встроенные функции стрелок в JSX
Использование функций стрелок или привязок в JSX - это плохая практика, которая снижает производительность, поскольку функция создается заново при каждом рендеринге.
-
Всякий раз, когда функция создается, предыдущая функция является сборщиком мусора. Повторное рендеринг многих элементов может создать рывок в анимации.
-
С помощью функции стрелка инлайн вызовет PureComponent
s, а также компоненты, которые используют shallowCompare
в shouldComponentUpdate
метод засавить в любом случае. Поскольку функция стрелки опора воссоздается каждый раз, поверхностное сравнение идентифицирует ее как изменение реквизита, и компонент будет перерисовываться.
Как вы можете видеть в следующих 2 примерах - когда мы используем встроенную функцию стрелки, компонент <Button>
перерисовывается каждый раз (консоль отображает текст "кнопка рендеринга").
Пример 1 - PureComponent без встроенного обработчика
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Ответ 2
Это связано с тем, что функция стрелки, по-видимому, создаст новый экземпляр функции для каждого рендеринга, если он используется в свойстве JSX. Это может создать огромную нагрузку на сборщик мусора и также помешает браузеру оптимизировать любые "горячие пути", поскольку функции будут выбрасываться вместо повторного использования.
Вы можете увидеть полное объяснение и получить дополнительную информацию по адресу https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
Ответ 3
Чтобы избежать создания новых функций с теми же аргументами, вы можете memoize результат связывания функции, вот простая утилита с именем memobind
, чтобы сделать это: https://github.com/supnate/memobind
Ответ 4
Использовать встроенные функции, как это, прекрасно. Правило линтинга устарело.
Это правило относится ко времени, когда функции стрелок были не такими распространенными, и люди использовали .bind (это), который раньше был медленным. Проблема производительности была исправлена в Chrome 49.
Обратите внимание, что встроенные функции не передаются в качестве подпорки дочернему компоненту.
Райан Флоренс, автор React Router, написал отличную статью об этом:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
Ответ 5
Вы можете использовать функции стрелок с помощью библиотекиact -cached-handler, не нужно беспокоиться о производительности повторного рендеринга:
Примечание: внутренне он кэширует ваши функции стрелок указанным ключом, не нужно беспокоиться о повторном рендеринге!
render() {
return <div>
{
this.props.photos.map(photo=>
<Photo key={photo.url}
onClick={this.handler(photo.url, (url) => {
console.log(url) })}
/>)
}
</div>
}
Другие преимущества:
- Именованные обработчики
- Обрабатывать события с помощью стрелок
- Доступ к ключу, пользовательским аргументам и исходному событию
- Компонент рендеринга производительности
- Пользовательский контекст для обработчиков