Почему реквизиты 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 - это плохая практика, которая снижает производительность, поскольку функция создается заново при каждом рендеринге.

  1. Всякий раз, когда функция создается, предыдущая функция является сборщиком мусора. Повторное рендеринг многих элементов может создать рывок в анимации.

  2. С помощью функции стрелка инлайн вызовет 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>

}

Другие преимущества:

  • Именованные обработчики
  • Обрабатывать события с помощью стрелок
  • Доступ к ключу, пользовательским аргументам и исходному событию
  • Компонент рендеринга производительности
  • Пользовательский контекст для обработчиков