Предотвращение привязки к каждой итерации в цикле рендеринга React
У меня есть компонент React, который отображает список файлов. Иногда список довольно длинный, и поскольку разбиение на страницы не является идеальным с точки зрения пользовательского интерфейса, в этом случае список файлов становится довольно медленным во время повторного рендеринга (например, при перетаскивании файлов для изменения их порядка).
Одним из факторов, способствующих замедлению, является то, что в цикле, который запускается один раз для каждого файла, имеется несколько bind()
звонки:
render() {
return (
<...>
{this.props.files.map((file, index) => {
return (
<tr
key={`${index}#${file.name}`}
onDragStart={this.onDragStart.bind(this, file, index)}
onDragEnter={this.onDragEnter.bind(this, file)}
onDragOver={this.onDragOver.bind(this, file)}
onDragLeave={this.onDragLeave.bind(this, file)}
onDrop={this.onDrop.bind(this, file, index)}
/>
);
})}
</...>
);
}
Эти привязки необходимы, чтобы обработчики перетаскивания знали, какой файл перетаскивается и куда он сбрасывается. Поскольку все эти привязки выполняются один раз для каждого из сотен файлов (даже если результирующие элементы оптимизированы и фактически никогда не визуализируются), я не удивлюсь, что все немного вяло.
Мне интересно, есть ли лучший способ сделать это, чтобы как-то передать необходимые данные за каждую итерацию этим функциям, не создавая уникальную привязку для каждой функции в каждой итерации.
У меня есть одно возможное решение, которое я опубликую как собственный ответ, однако я был бы признателен за отзыв о том, является ли это решение лучше или хуже, и есть ли у него какие-либо недостатки.
1 ответ
Поэтому мое решение заключалось в том, чтобы связать функции один раз в конструкторе, как это принято в обычной практике, а затем поместить данные для каждой итерации в <tr/>
Сам элемент DOM.
Когда функции вызываются, браузер передает Event
объект, который содержит currentTarget
свойство, указывающее на узел DOM, к которому подключен обработчик событий, что позволяет снова извлекать данные за каждую итерацию.
Это позволяет снова и снова использовать одни и те же функции (связанные только один раз в конструкторе) с помощью нескольких визуализаций без дополнительной привязки.
Единственный недостаток этого метода - то, что объекты не могут быть присоединены как атрибуты DOM, только строки. В моем случае я бросил file
объект и застрял с числовым index
и использовал его для поиска file
возражать только тогда, когда это было необходимо.
constructor() {
// Functions are now bound only once during construction
this.onDragStart = this.onDragStart.bind(this);
this.onDragEnter = this.onDragEnter.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.onDragLeave = this.onDragLeave.bind(this);
this.onDrop = this.onDrop.bind(this);
}
onDragStart(event) {
// 'index' is recovered from the DOM node
const index = parseInt(event.currentTarget.dataset.index);
console.log('Event with index', index);
// Get back the 'file' object (unique to my code, but showing that
// I could not pass an object through this method and thus had to
// retrieve it again.)
const file = (index >= 0) ? this.props.files[index] : null;
}
// Same for other onXXX functions
// No more binds!
render() {
return (
<...>
{this.props.files.map((file, index) => {
return (
<tr
key={`${index}#${file.name}`}
data-index={index}
onDragStart={this.onDragStart}
onDragEnter={this.onDragEnter}
onDragOver={this.onDragOver}
onDragLeave={this.onDragLeave}
onDrop={this.onDrop}
/>
);
})}
</...>
);
}