我怎样才能加速生成巨大 DOM 的 React render() 方法
How can I speed up a React render() method that generates a huge DOM?
HtmlTable
组件
想象一下一个简单的 HtmlTable
React 组件。它根据通过 prop 传递给它的二维数组渲染数据data
还可以通过 rowCount
和 colCount
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 <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
道具,以<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);
}
因此,如果用户传递值为 {1: 10}
的 rowHeights
prop,我们只需要更新一行 - 第二行。
性能问题
但是,为了执行差异,React 必须重新运行整个渲染方法并重新创建数以万计的<tr>
。这对于大型数据集来说非常慢。
我想过使用 shouldComponentUpdate
,但它无济于事——瓶颈发生在我们甚至尝试更新<tr>
之前。瓶颈发生在重新创建整个表以执行差异期间。
我想到的另一件事是缓存render
结果,然后splice
更改的行,但这似乎完全违背了使用 React 的目的。
有没有办法不重新运行"大型"render
函数,如果我知道只有一小部分会改变?
编辑:显然,缓存是要走的路...例如,这是对 React 的 Github 中类似问题的讨论。React Virtualized似乎正在使用单元格缓存(尽管我可能遗漏了一些东西(。
编辑2:不是真的。存储和重用组件的"标记"仍然很慢。其中大部分来自协调 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
}
对于这种特殊情况,我建议其他人提到的反应虚拟化或固定数据表。这两个组件都将通过延迟加载数据来限制 DOM 交互,即仅呈现表中可见的部分。
更一般地说,MobX 文档有一个关于反应性能的优秀页面。请检查一下。以下是要点。
1.使用许多小组件
Mobx-React的
@observer
组件将跟踪他们使用的所有值,并在其中任何一个发生变化时重新渲染。因此,组件越小,它们需要重新渲染的更改就越小;这意味着用户界面的更多部分可以彼此独立地呈现。
observer
允许组件独立于其父级进行渲染
2. 在专用组件中渲染列表
在渲染大型集合时尤其如此。众所周知,React 在渲染大型集合方面非常糟糕,因为协调器必须在每次集合更改时评估集合生成的组件。因此,建议使用仅映射集合并呈现它的组件,而不呈现任何其他组件:
3. 不要使用数组索引作为键
不要使用数组索引或任何将来可能更改的值作为键。如果需要,为您的对象生成 id。另请参阅此博客。
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);
来源:
- https://mobxjs.github.io/mobx/best/react-performance.html
- https://github.com/mobxjs/mobx-react#faq
- https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
将每一行作为子组件,并将 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;
}
}
您应该调查如何在工作线程(或其池(中执行协调并将结果传输回主线程。有一个项目试图做到这一点,因此您可以尝试它们的实现。
请注意,我属于从未遇到过 React 此类需求的大多数人,所以我无法提供任何实现细节或进一步提示,但我相信web-perf/react-worker-dom
可能是开始探索和发现类似解决方案的好地方。
这里的问题是更改内联样式..也许只是在表外使用特定<tr>
的样式。
- 生成表,将类名或 ID 添加到
<tr>
- 生成样式表并将它们注入
<head>
。重复 2.在不更改表的情况下执行步骤
(只需使用 js 生成 CSS 文本(
.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);
这个问题并不那么简单。但是,如果它只与样式相关,则将行样式也保留在父表标签中。
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'));
- React DOM offsetHeight before rendering
- 如何让React JS点击处理程序在执行时更新DOM
- React.js如何使用虚拟DOM加速渲染
- 从子级获取React dom组件
- DOM Attributes and React
- React - 从 DOM 元素获取组件以进行调试
- React:是否可以访问DOM元素's道具
- React:将组件附加到DOM
- React-从简单对象树创建dom
- 来自jQuery,如何在React.js中制作DOM动画?例如设置进度条的动画
- react-在不破坏封装的情况下访问DOM
- React.JS在虚拟DOM中计算img宽度
- 更改表结构会导致 React 失去对 DOM 的跟踪
- ReactJS:如何从 DOM 更新 React
- 无法使用服务器端渲染访问DOM-react 0.14.1、react DOM 0.14.1和react router 1
- 尝试使用 React.DOM 来设置正文样式
- 错误:无法解析模块'react-dom'
- 如何有条件地添加属性到react DOM元素
- react dom/server是否在客户端工作
- React.DOM.p(null, message) - explain null?