Нарушение производительности при создании обработчиков на каждом рендере с помощью перехватчиков реакции - PullRequest
0 голосов
/ 16 ноября 2018

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

Вопрос, который возник во время эксперимента, заключался в том, насколько дорого всегда создавать новую функцию-обработчик, чтобы просто выбросить ее при использовании useCallback.

Учитывая этот пример:

const MyCounter = ({initial}) => {
    const [count, setCount] = useState(initial);

    const increase = useCallback(() => setCount(count => count + 1), [setCount]);
    const decrease = useCallback(() => setCount(count => count > 0 ? count - 1 : 0), [setCount]);

    return (
        <div className="counter">
            <p>The count is {count}.</p>
            <button onClick={decrease} disabled={count === 0}> - </button>
            <button onClick={increase}> + </button>
        </div>
    );
};

Хотя я обертываю обработчик в useCallback, чтобы избежать передачи нового обработчика каждый раз, когда он выполняет рендеринг, встроенную стрелку все равно нужно создавать, только чтобы отбрасывать в большинстве случаев.

Вероятно, не имеет большого значения, если я рендую только несколько компонентов. Но насколько велико влияние на производительность, если я делаю это тысячи раз? Есть ли заметное снижение производительности? И как бы избежать этого? Возможно, фабрика статических обработчиков вызывается только тогда, когда нужно создать новый обработчик?

Ответы [ 3 ]

0 голосов
/ 16 ноября 2018

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

<button onClick={(e) => this.handleClick(e)}>click me!</button>
<button onClick={this.handleClick.bind(this)}>click me!</button>

Оба эквивалентны.Аргумент e, представляющий событие React, в то время как с функцией стрелки мы должны передать его явно, с помощью bind любые аргументы автоматически передаются.

0 голосов
/ 19 ноября 2018

Но насколько велико влияние на производительность, если я делаю это 1000 раз? Есть ли заметное снижение производительности?

Это зависит от приложения. Если вы просто визуализируете 1000 строк счетчиков, это, вероятно, нормально, как видно из фрагмента кода ниже. Обратите внимание, что если вы просто изменяете состояние отдельного <Counter />, только этот счетчик будет перерисован, остальные 999 счетчиков не будут затронуты.

Но я думаю, что вы обеспокоены несущественными вещами здесь. В реальных приложениях вряд ли будет отображаться 1000 элементов списка. Если ваше приложение должно отображать 1000 элементов, возможно, что-то не так с вашим дизайном.

  1. Вы не должны отображать 1000 предметов в DOM. Обычно это плохо с точки зрения производительности и UX, с современными JavaScript-фреймворками или без них Вы можете использовать методы работы с окнами и отображать только те элементы, которые видите на экране, остальные элементы вне экрана могут находиться в памяти.

  2. Реализуйте shouldComponentUpdate (или useMemo), чтобы другие элементы не перерисовывались в случае повторного рендеринга компонента верхнего уровня.

  3. Используя функции, вы избегаете накладных расходов на классы и некоторых других связанных с классами вещей, о которых вы не знаете, потому что React делает это автоматически. Вы теряете некоторую производительность из-за вызова некоторых хуков в функциях, но вы получаете некоторую производительность и в других местах.

  4. Наконец, обратите внимание, что вы вызываете хуки useXXX и не выполняете функции обратного вызова, которые вы передали в хуки. Я уверен, что команда React хорошо поработала над тем, чтобы облегчить вызовы с помощью ловушек, вызовы не должны быть слишком дорогими.

А как можно было бы избежать этого?

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

Наконец, я хотел бы повторить, что вам не следует беспокоиться об этой проблеме, потому что вы должны стараться избегать попадания в такую ​​ситуацию вместо того, чтобы справляться с ней, опираясь на такие методы, как использование окон и разбиение на страницы - только загружая данные, которые нужно отобразить на текущей странице.

const Counter = ({ initial }) => {
  const [count, setCount] = React.useState(initial);

  const increase = React.useCallback(() => setCount(count => count + 1), [setCount]);
  const decrease = React.useCallback(
    () => setCount(count => (count > 0 ? count - 1 : 0)),
    [setCount]
  );

  return (
    <div className="counter">
      <p>The count is {count}.</p>
      <button onClick={decrease} disabled={count === 0}>
        -
      </button>
      <button onClick={increase}>+</button>
    </div>
  );
};

function App() {
  const [count, setCount] = React.useState(1000);
  return (
    <div>
      <h1>Counters: {count}</h1>
      <button onClick={() => {
        setCount(count + 1);
      }}>Add Counter</button>
      <hr/>
      {(() => {
        const items = [];
        for (let i = 0; i < count; i++) {
          items.push(<Counter key={i} initial={i} />);
        }
        return items;
      })()}
    </div>
  );
}


ReactDOM.render(
  <div>
    <App />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
0 голосов
/ 16 ноября 2018

Ответы на часто задаваемые вопросы дают объяснение этому

Являются ли хуки медленными из-за создания функций в рендере?

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

Кроме того, учтите, что конструкция крючков более эффективна в пара способов:

Крючки избегают больших накладных расходов, которые требуются классам, таких как стоимость создания экземпляров класса и привязки обработчиков событий в Конструктор.

Идиоматическому коду с использованием Hooks не требуется глубокое дерево компонентов Вложение, которое распространено в кодовых базах, которые используют более высокий порядок компоненты, рендеринг реквизита и контекст. С меньшими деревьями компонентов, У Реакта меньше работы.

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

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

Более того, для функциональных компонентов вы можете оптимизировать, используя useMemo, чтобы компоненты выполняли повторный рендеринг, когда их реквизиты не изменились.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...