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