Чому реквізити JSX не повинні використовувати функції стрілок або прив'язувати їх?


103

Я запускаю ворсинку зі своїм додатком 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>
))}

Це погана практика, якої слід уникати? І який найкращий спосіб це зробити?

Відповіді:


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 або перетворення властивостей класу до вашої конфігурації бабеля.

    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) }
adam-beck

4
@ adam-beck - додайте його до визначення методу зворотного виклику у класі cb() { onTodoClick(this.props.todo.id); }.
Ori Drori

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

"Створення нових екземплярів функції кожного разу означає, що стан змінюється", що ви маєте на увазі під цим? У питанні
взагалі

4

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


4

Використовувати вбудовані функції, як це, ідеально добре. Правило підшивки застаріле.

Це правило починається з того часу, коли функції стрілок були не такими поширеними, а люди використовували .bind (this), який раніше був повільним. Питання щодо продуктивності виправлено в Chrome 49.

Зверніть увагу, що ви не передаєте вбудовані функції в якості реквізиту до дочірнього компонента.

Райан Флоренція, автор React Router, написав про це чудовий твір:

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


Чи можете ви, будь ласка, показати, як написати одиничний тест на компоненти із вбудованими стрілковими функціями?
krankuba

1
@krankuba Це питання не стосувалося цього. Ви все одно можете передавати анонімні функції, які не визначені вбудованим, але все ще не перевіряються.
sbaechler

-1

Ви можете використовувати функції стрілок, використовуючи бібліотеку обробників реагування , не потрібно турбуватися про ефективність повторного відтворення:

Примітка: Внутрішньо він кешує ваші функції стрілок вказаною клавішею, не потрібно турбуватися про повторне відображення!

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 роботою з виправленими тут і функціями стрілок або є рідною справою, або перекладені бабелем на звичайні функції; в обох випадках можна припустити, що це не повільно. ( Реактивне навчання )

Я вважаю, що люди, які претендують на створення функцій дорого, завжди були дезінформованими (команда React цього ніколи не казала) ( Tweet )

Коли 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.