Почему JSX-реквизиты не должны использовать стрелочные функции или привязку?

105

Я запускаю 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>
))}

Это плохая практика, которой следует избегать? И как лучше всего это сделать?

KadoBOT
источник

Ответы:

170

Почему вы не должны использовать встроенные стрелочные функции в JSX-свойствах

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

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

  2. Использование встроенной стрелочной функции в любом случае приведет к повторной отрисовке PureComponents и компонентов, которые используются shallowCompareв shouldComponentUpdateметоде. Поскольку свойство стрелочной функции воссоздается каждый раз, поверхностное сравнение идентифицирует его как изменение свойства, и компонент будет повторно отображен.

Как вы можете видеть в следующих двух примерах - когда мы используем встроенную стрелочную функцию, <Button>компонент каждый раз повторно визуализируется (на консоли отображается текст кнопки рендеринга).

Пример 1 - PureComponent без встроенного обработчика

Пример 2 - PureComponent со встроенным обработчиком

Привязка методов к thisфункциям стрелок без встраивания

  1. Связывание метода вручную в конструкторе:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Связывание метода с использованием полей класса предложения с функцией стрелки. Поскольку это предложение этапа 3, вам необходимо добавить предустановку этапа 3 или преобразование свойств класса в конфигурацию babel.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Компоненты функций с внутренними обратными вызовами

Когда мы создаем внутреннюю функцию (например, обработчик событий) внутри функционального компонента, функция будет воссоздаваться каждый раз, когда компонент визуализируется. Если функция передается как реквизиты (или через контекст) дочернему компоненту ( Buttonв данном случае), этот дочерний элемент также будет повторно отрисован.

Пример 1 - Функциональный компонент с внутренним обратным вызовом:

Чтобы решить эту проблему, мы можем обернуть обратный вызов useCallback()хуком и установить зависимости в пустой массив.

Примечание:useState генерируются функция принимает функцию получения обновлений, что обеспечивает текущее состояние. Таким образом, нам не нужно устанавливать зависимость от текущего состояния useCallback.

Пример 2 - Функциональный компонент с внутренним обратным вызовом, заключенный в useCallback:

Ори Дрори
источник
3
Как этого добиться в компонентах без состояния?
люкс,
4
Компоненты без состояния (функции) не имеют this, поэтому связывать нечего. Обычно методы предоставляются интеллектуальным компонентом оболочки.
Ори Дрори
39
@OriDrori: Как это работает, когда вам нужно передать данные в обратном вызове? onClick={() => { onTodoClick(todo.id) }
адам-бек
4
@ adam-beck - добавьте его в определение метода обратного вызова в классе cb() { onTodoClick(this.props.todo.id); }.
Ори Дрори
2
@ adam-beck Я думаю, это как использовать useCallbackс динамическим значением. stackoverflow.com/questions/55006061/…
Шота Тамура
9

Это связано с тем, что стрелочная функция, очевидно, будет создавать новый экземпляр функции при каждом рендеринге, если она используется в свойстве JSX. Это может создать огромную нагрузку на сборщик мусора, а также помешать браузеру оптимизировать любые «горячие пути», поскольку функции будут выброшены, а не использованы повторно.

Вы можете увидеть все объяснение и дополнительную информацию на https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Карл-Йохан Сьегрен
источник
Не только это. Создание новых экземпляров функции каждый раз означает, что состояние изменяется, и когда состояние компонента изменяется, оно будет повторно отображаться. Поскольку одна из основных причин использовать React - визуализировать только те элементы, которые меняются, использование bindфункций или стрелок здесь означает выстрел себе в ногу. Однако это плохо документировано, особенно в случае работы с mapмассивами ping в списках и т. Д.
hippietrail
«Создание новых экземпляров функции каждый раз означает изменение состояния», что вы имеете в виду? Там нет никакого государства в вообще в вопросе
apieceofbart
4

Чтобы избежать создания новых функций с теми же аргументами, вы можете запомнить результат привязки функции, вот простая утилита, названная memobindдля этого: https://github.com/supnate/memobind

supNate
источник
4

Использование таких встроенных функций совершенно нормально. Правило линтинга устарело.

Это правило пришло из тех времен, когда стрелочные функции были не так распространены, и люди использовали .bind (this), который раньше был медленным. Проблема с производительностью исправлена ​​в Chrome 49.

Обратите внимание, что вы не передаете встроенные функции в качестве свойств дочернему компоненту.

Райан Флоренс, автор React Router, написал об этом отличную статью:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

Sbaechler
источник
Не могли бы вы показать, как написать модульный тест для компонентов со встроенными стрелочными функциями?
krankuba
1
@krankuba Вопрос не об этом. Вы по-прежнему можете передавать анонимные функции, которые не определены встроенными, но по-прежнему не тестируются.
sbaechler
-1

Вы можете использовать стрелочные функции с помощью библиотеки react-cached-handler , не нужно беспокоиться о производительности повторного рендеринга:

Примечание: внутренне он кэширует ваши стрелочные функции по указанному ключу, не нужно беспокоиться о повторном рендеринге!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Другие свойства:

  • Именованные обработчики
  • Обработка событий с помощью стрелочных функций
  • Доступ к ключу, настраиваемым аргументам и исходному событию
  • Производительность рендеринга компонентов
  • Пользовательский контекст для обработчиков
Гоминеджад
источник
Вопрос был в том, почему мы не можем его использовать. Не как использовать это с каким-нибудь другим хаком.
капил
-1

Почему JSX-реквизиты не должны использовать стрелочные функции или привязку?

В основном потому, что встроенные функции могут нарушить мемоизацию оптимизированных компонентов:

Традиционно проблемы с производительностью встроенных функций в React были связаны с тем, как передача новых обратных вызовов при каждом рендеринге нарушает shouldComponentUpdateоптимизацию дочерних компонентов. ( документы )

Речь идет о меньших затратах на создание дополнительных функций:

Function.prototype.bind Здесь исправлены проблемы с производительностью, а стрелочные функции либо встроены, либо передаются с помощью babel в простые функции; в обоих случаях можно предположить, что он не медленный. ( Обучение React )

Я считаю, что люди, утверждающие, что создание функций стоит дорого, всегда были дезинформированы (команда React никогда этого не говорила). ( Твитнуть )

Когда это react/jsx-no-bindправило полезно?

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

  • React.memo (для функциональных компонентов)
  • PureComponentили пользовательский shouldComponentUpdate(для компонентов класса)

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

Как решить ошибку ESLint?

Классы: определите обработчик как метод или свойство класса для thisпривязки.
Крючки: Использование useCallback.

Середина

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

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
источник