Предотвращение привязки к каждой итерации в цикле рендеринга 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}
                />
            );
        })}
        </...>
    );
}
Другие вопросы по тегам