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