Выдержки из обзора кода React
Это« отрывок
из того, что я обнаружил, что писал в обзорах кода более одного раза или с ощущением, что… средний .com »



Проблема

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

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

class Main extends React.Component {
  render() {
    return (
      <div>
        {[1, 2, 3].map(
          i => <Click
                 callback={() => console.log('triggered', i)}
               />
        )}
      </div>
    )
  }
}
const Click = () => <div onClick={this.props.callback} />

В любом случае, привязка this (неявная или явная) происходит, новый объект функции создается каждый раз, когда React выполняет алгоритм согласования. Тем не менее, с каждым ‹div /›, ‹span /› или ‹YourComponent /›, отображаемым любой функцией render, просто для создания вывода вы также создаете объекты. Так зачем возиться с одним / двумя / тремя /… объектами, созданными путем привязки какой-либо функции? Нам нужно углубиться. В буквальном смысле - глубже в дереве компонентов. Еще несколько предметов на этом уровне в большинстве случаев не повредит.

Компоненты можно оптимизировать (как и все), чтобы они избегали ненужных последовательных отрисовок, и это можно сделать, создав PureComponent вместо Component или реализовав собственный shouldComponentUpdate, который сравнивает следующие свойства с текущими. Именно то, как это делается, мешает новому объекту функции, предоставляемому продукту итерации компонентов при каждом рендеринге - дочерний элемент видит и сравнивает свойства, которые никогда не бывают одинаковыми!

Исключение

В этом вопросе есть одно «но», и его просто нельзя пропустить. Это предупреждение, чтобы вы не загоняли себя в угол. Я думаю об опасности преждевременной оптимизации 👻. Решение, которое вы видите выше, будет просто отличным, если вы не видите проблем с производительностью (мусор из-за обработки большой части дерева компонентов или выделений и длинные перерывы в сборке мусора) на target машины.

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

В случае, когда вам абсолютно необходимо отрендерить некоторые компоненты тысячи раз или просто много разных компонентов (например, анимация с помощью React), попробуйте минимальный вывод render и некоторые прямые манипуляции. Если вы не можете или просто не готовы идти ТАК глубоко или использовать shouldComponentUpdate на уровне, который генерирует сумму, которая сама по себе приведет к огромным вычислениям - это то место, куда вы переместитесь оптимизация ниже, и для этого вам, возможно, придется избегать создания новых функций.

Решение 1. Компонент, поддерживающий итерацию

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

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

Я сделаю ссылку на предыдущий пример, чтобы было понятно, какова цель этого нового компонента:

class Main extends React.Component {
  // don't know what it is?
  // http://2ality.com/2017/07/class-fields.html
  log = (i) => {
    console.log('triggered', i)
  }
  render() {
    return (
      <div>
        {[1, 2, 3].map(
          i => <Click n={i} callback={this.log} />
        )}
      </div>
    )
  }
}
class Click extends React.PureComponent {
  handleClick = () => {
    this.props.callback(this.props.n)
  }
  render() {
    return <div onClick={this.handleClick} />
  }
}

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

Решение 2: мемоизация

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

Идея состоит в том, чтобы - опять же - не создавать функцию при каждом вызове render. Здесь предполагается (и не очень изощренное), что вы можете создать и передать функцию, которая имеет только фиксированные: а) стабильный источник списка (не сам список!) Б) индекс элемента в указанном списке. Причина не исправления списка напрямую состоит в том, чтобы быть устойчивым к сценариям, когда список изменяется, что также может иметь место для каждого вызова render, но более вероятно из-за события, для которого нам нужен этот обратный вызов. В основном это будет связано с тем, что вам понадобится экземпляр компонента, предполагая, что массив предоставляется в реквизитах или находится в состоянии компонента.

Это может быть немного размыто, потому что это не полное описание, но что может быть лучше, чем на самом деле показать, о чем идет речь?

class Component extends React.Component {
  list = [1, 2, 3]
  loggers = []
  log(idx) { console.log(this.list[idx]) }
  
  render() {
    return (
      <div>
        {this.list.map(
          (i, idx) => (
            <div
              key={i}
              onClick={
                this.loggers[idx] ||
                this.loggers[idx] = () => this.log(idx)
              }
            />
          )
        )}
      </div>
    )
  }
}

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

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

Это «отрывок» из того, что я писал в обзорах кода более одного раза, или с чувством, что это что-то важное, или по совершенно другой причине. По-прежнему называет это так. Позвони 🚔 в полицию 🚔, если хочешь.
🌟 Следуй за мной, чтобы узнать больше. 🌟