Подтвердить что ты не робот

Как я могу ускорить метод React render(), который генерирует огромную DOM?

Компонент HtmlTable

Представьте себе простой компонент HtmlTable React. Он отображает данные на основе двумерного массива, переданного ему через data prop, а также может ограничивать количество столбцов и строк через реквизиты rowCount и colCount. Кроме того, нам нужен компонент для обработки огромных массивов данных (десятки тысяч строк) без разбивки на страницы.

class HtmlTable extends React.Component {
    render() {
        var {rowCount, colCount, data} = this.props;
        var rows = this.limitData(data, rowCount);

        return <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                return <tr key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    shouldComponentUpdate() {
        return false;
    }

    limitData(data, limit) {
        return limit ? data.slice(0, limit) : data;
    }
}

Реквизиты rowHeights

Теперь мы хотим, чтобы пользователь менял высоту строки и выполнял ее динамически. Добавим rowHeights prop, который представляет собой карту индексов строк для высоты строк:

{
    1: 100,
    4: 10,
    21: 312
}

Мы меняем наш метод render, чтобы добавить style prop к <tr>, если для его индекса указана высота, и мы также используем shallowCompare для shouldComponentUpdate):

    render() {
        var {rowCount, colCount, data, rowHeights} = this.props;
        var rows = this.limitData(data, rowCount);

        return <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                var style = rowHeights[i] ? {height: rowHeights[i] + 'px'} : void 0;
                return <tr style={style} key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    shouldComponentUpdate(nextProps, nextState) {
        return shallowCompare(this, nextProps, nextState);
    }

Итак, если пользователь передает подсказку rowHeights со значением {1: 10}, нам нужно обновить только одну строку - вторую.

Проблема производительности

Однако, чтобы выполнить diff, React должен был повторно запустить весь метод render и воссоздать десятки тысяч <tr> s. Это очень медленно для больших наборов данных.

Я думал об использовании shouldComponentUpdate, но это не помогло бы - узкое место произошло, прежде чем мы даже попытались обновить <tr> s. Узкое место происходит во время отдыха всей таблицы, чтобы сделать разницу.

Еще одна вещь, о которой я думал, - это кеширование результата render, а затем splice изменение строк, но, похоже, оно вообще не позволяет использовать React.

Есть ли способ не перезапускать "большую" render функцию, если я знаю, что изменится только ее крошечная часть?

Изменить: По-видимому, кэширование - это путь... Например, здесь обсуждение аналогичной проблемы в React Github. И React Virtualized, похоже, использует кеш ячейки (хотя, возможно, я что-то пропустил).

Edit2: Не совсем. Хранение и повторное использование "разметки" компонента все еще медленное. Большинство из них происходит от согласования DOM, чего я должен был ожидать. Ну, теперь я полностью потерян. Это то, что я сделал для подготовки "разметки":

    componentWillMount() {
        var {rowCount, colCount, data, rowHeights={}} = this.props;
        var rows = this.limitData(data, rowCount);
        this.content = <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                var style = rowHeights[i] ? {height: rowHeights[i] + 'px'} : void 0;
                return <tr style={style} key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    render() {
        return this.content
    }
4b9b3361

Ответ 1

В этом конкретном случае я бы рекомендовал react-virtualized или fixed-data-table, как упоминалось другими. Оба компонента ограничивают взаимодействие с DOM, ленивыми данными загрузки, т.е. Отображают только часть видимой таблицы.

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

1. Используйте много небольших компонентов

mobx-react @observer компоненты будут отслеживать все значения, которые они используют и повторно отображают, если какое-либо из них изменяется. Таким образом, чем меньше ваши компоненты, тем меньше изменений, которые они должны выполнить повторно; это означает, что больше частей вашего пользовательского интерфейса имеют возможность визуализации независимо друг от друга.

observer позволяет компонентам визуализировать независимо от их родительского

2. Отображение списков в выделенных компонентах

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

3. Не используйте индексы массива как клавиши

Не используйте индексы массивов или любое значение, которое может измениться в будущем как ключ. При необходимости создайте идентификатор для ваших объектов. Смотрите также этот блог.

4. Значения разницы в последнее время

При использовании mobx-react рекомендуется разыменовывать значения как можно дольше. Это связано с тем, что MobX будет повторно отображать компоненты, которые автоматически разыскивают наблюдаемые значения. Если это происходит глубже в вашем дереве компонентов, меньше компонентов необходимо повторно отобразить.

5. Функция привязки ранних

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

Пример (непроверенный)

import { observer } from 'mobx-react';
import { observable } from 'mobx';


const HtmlTable = observer(({
  data,
}) => {
  return (
    <table>
      <TBody rows={data} />
    </table>
  );
}

const TBody = observer(({
  rows,
}) => {
  return (
    <tbody>
      {rows.map((row, i) => <Row row={row} />)}
    </tbody>
  );
});

const Row = observer(({
  row,
}) => {
  return (
    <tr key={row.id} style={{height: row.rowHeight + 'px'}}>
      {row.cols.map((cell, i) => 
        <td key={cell.id}>{cell.value}</td>
      )}
    </tr>
  );
});

class DataModel {
  @observable rows = [
    { id: 1, rowHeight: 10, cols: [{ id: 1, value: 'one-1' }, { id: 2, value: 'one-2' }] },
    { id: 2, rowHeight: 5,  cols: [{ id: 1, value: 'two-1' }, { id: 2, value: 'two-2' }] },
    { id: 3, rowHeight: 10,  cols: [{ id: 1, value: 'three-1' }, { id: 2, value: 'three-2' }] },
  ];
  @observable rowLimit = 10;
  @observable colLimit = 10;

  @computed
  get limitedRows() {
    return this.limitData(this.rows, this.rowLimit).map(r => {
      return { id: r.id, cols: this.limitData(r.col, this.colLimit) };
    });
  }

  limitData(data, limit) {
    return limit ? data.slice(0, limit) : data;
  }
}

const data = new DataModel();

React.render(<HtmlTable data={data.limitedRows} />, document.body);

Источники:

Ответ 2

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

class HtmlTable extends React.Component {
   render() {
       var {rowCount, colCount, data} = this.props;
       var rows = this.limitData(data, rowCount);

       return <table>
           <tbody>{rows.map((row, i) => {
               var cols = this.limitData(row, colCount);

               return (<Row cols={cols} key={i}/>)
           })}</tbody>
       </table>
   }

   shouldComponentUpdate() {
       return false;
   }

   limitData(data, limit) {
       return limit ? data.slice(0, limit) : data;
   }
}

Ответ 3

Вам следует выяснить, как выполнить сверку внутри работника (или его пул) и перенести результат обратно в основной поток. Там существует проект, который пытается сделать именно это, поэтому вы можете поэкспериментировать с их реализацией.

Обратите внимание, что я принадлежу к большинству, которое никогда не сталкивалось с такими требованиями с React как самостоятельно, поэтому я не могу предоставить какие-либо детали реализации или дальнейшие намеки, но я считаю, что web-perf/react-worker-dom может быть хорошим местом для начала изучая и обнаруживая подобные решения.

Ответ 4

Проблема заключается в изменении стилей inline. Возможно, просто используйте стили для конкретного <tr> вне таблицы.

  • Сгенерируйте таблицу, добавьте имена классов или идентификаторы в <tr>
  • Создайте таблицы стилей и введите их в <head>

... повторите шаг 2. без изменения таблицы

(просто сгенерируйте текст css с помощью js)

.tr-0, #tr-0, .tr-1, ... { height: 10px }
...

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

Вы можете использовать некоторые действительно простые js для генерации <style> и поместить их в голову:

var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.tr-0 { height: 10px; }';
// write some generator for the styles you have to change and add new style tags to the head...
document.getElementsByTagName('head')[0].appendChild(style);

Ответ 5

Я знаю, что эта проблема не так проста. Но если это связано только с стилем, то сохраняйте стили строк также в теге parent-table.

https://codepen.io/shishirarora3/pen/grddLm

class Application extends React.Component {
  renderStyle=(x,y)=> {
    console.log(x,y);
    let styleCode = `table tr:nth-child(${x}){ height: ${y} }`
    console.log(styleCode);
    return (
        <style>{ styleCode }</style>
    )
};
  render() {
    return <div>
      {this.renderStyle(1,'100px')}
      <table>
        <tr><td>1</td></tr>
        <tr><td>2</td></tr>
        <tr><td>3</td></tr>
      </table>

    </div>;
  }
}

/*
 * Render the above component into the div#app
 */
React.render(<Application />, document.getElementById('app'));