Компонент 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
}