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
道具,它是行索引到行高的地图:
{
1: 100,
4: 10,
21: 312
}
如果为其索引指定了高度,我们会更改render
方法以向style
道具添加<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}}道具,我们只需要更新一行 - 第二行。
但是,为了做差异,React必须重新运行整个render方法并重新创建成千上万的{{1}} s。这对于大型数据集来说非常慢。
我考虑使用{1: 10}
,但它没有帮助 - 瓶颈发生在我们甚至尝试更新<tr>
之前。在整个表的重新创建过程中发生了瓶颈,以便进行差异化。
我想到的另一件事是缓存shouldComponentUpdate
结果,然后<tr>
更改了行,但似乎完全没有使用React的目的。
有没有办法不重新运行“大”render
函数,如果我知道只有一小部分函数会改变?
编辑:显然,缓存是要走的路......例如,这里的a discussion of a similar problem in React's Github.和React Virtualized似乎正在使用a cell cache(尽管我可能会遗漏东西)。
Edit2:不是。存储和重用组件的“标记”仍然很慢。其中大部分来自协调DOM,这是我应该期待的。好吧,现在我完全迷失了。这就是我准备“标记”所做的:
splice
答案 0 :(得分:17)
对于这种特殊情况,我建议其他人提及react-virtualized或fixed-data-table。两个组件都将通过延迟加载数据来限制DOM交互,即仅渲染表中可见的部分。
更一般地说,MobX documentation有一个excellent page on react performance。请检查一下。以下是要点。
mobx-react的
@observer
组件将跟踪他们使用的所有值,并在其中任何值发生更改时重新呈现。因此,组件越小,重新渲染的变化就越小;这意味着您的用户界面的更多部分可以彼此独立呈现。呈现
observer
允许组件独立于其父
在渲染大型集合时尤其如此。 React在渲染大型集合方面是出了名的不好,因为协调程序必须在每次集合更改时评估集合生成的组件。因此,建议使用仅映射集合并对其进行渲染的组件,并不再渲染其他内容:
请勿使用数组索引或将来可能更改的任何值作为键。如果需要,为您的对象生成id。另请参阅此blog。
使用mobx-react时,建议尽可能晚取消引用值。这是因为MobX将重新呈现自动取消引用可观察值的组件。如果在组件树中发生更深层次的事情,则需要重新渲染的组件越少。
这个技巧通常适用于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);
来源:
答案 1 :(得分:2)
将每一行作为子组件并将props传递给该子组件。 该子组件将具有自己的状态,并且可以在不影响所有行的情况下进行更改
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;
}
}
答案 2 :(得分:2)
您应该调查如何在工作者(或其池)中执行协调,并将结果传回主线程。有exists a project试图做到这一点,所以你可以试验他们的实现。
请注意,我所属的大多数人从未遇到过像React这样的要求,所以我无法提供任何实施细节或进一步的提示,但我相信web-perf/react-worker-dom
可能是一个好地方开始探索并发现类似的解决方案。
答案 3 :(得分:2)
这里的问题是更改内嵌样式..也许只使用表格外的特定<tr>
的样式..
<tr>
<head>
...重复2.步骤而不更改表格
(只用js生成css文本)
.tr-0, #tr-0, .tr-1, ... { height: 10px }
...
我没有对它进行测试,这只是一个想法,但我认为这可能是一种方法,如果你不想在渲染后触摸生成的表格。
您可以使用一些非常简单的j来生成<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);
答案 4 :(得分:1)
我知道这个问题并不那么简单。但如果它只与样式相关,那么在父表标记中也保留行的样式。
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'));