Предотвращение привязки к каждой итерации в цикле рендеринга React - PullRequest
0 голосов
/ 29 декабря 2018

У меня есть компонент 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 Ответ

0 голосов
/ 29 декабря 2018

Таким образом, мое решение заключалось в том, чтобы связать функции один раз в конструкторе, как это обычно делается, а затем поместить данные для каждой итерации в сам элемент <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}
                />
            );
        })}
        </...>
    );
}
...