React嵌套数据渲染性能优化及“memo疑问”
场景
业务上是动态渲染n
个主机节点池信息,选择某个磁盘后显示配置下拉框,选中当前选择磁盘的配置。遇到场景问题是单选选中后右侧配置下拉框2s+
才能出现,初步定位是“React需要深度比较对象来找出差异,这可能会非常耗时,尤其是在对象结构复杂且层级深的情况下”。
数据结构如下:
// pools数据结构
[
{
"e3cf14fc-0eba-4226-af0d-1f6462dc3ae7": {
"disk_id": "xxxx",
"hostname": "hostname1",
"poolList": [
{
"device_node": "/etc/dist1",
"size": 500,
"visibleFS": false,
"fstype": "",
"default_fstype": "xfs"
}
]
}
}
]
// 单选回调逻辑
handlePool = (e, host) => {
this.setState((prevState) => {
const newState = {
pools: prevState.pools,
};
newState.pools[host.id].disk_id = e.target.value;
newState.pools[host.id].hostname = host.name;
newState.pools[host.id].poolList.forEach((v) => {
if (v.uuid === e.target.value) v.visibleFS = true;
else if (v.visibleFS) v.visibleFS = false;
});
return newState;
});
};
尝试优化
按照官方推荐如下
React.memo
(对于函数组件)- 重写
shouldComponentUpdate
(对于类组件)
渲染初步分析
按照业务模型套用优化。首先确定渲染区域,单选时只会重新渲染下图区域。数据结构更新在pools[host.id]
这一层。
进一步缩小渲染区域
因为单选操作缘故,渲染只会影响之前单选区域和当前单选区域。如下图绿框标注。
但是按照最小渲染区域提出函数组件,只能按照红框区域拆分。无法拆分到绿框最小渲染区域(Radio.Group)。
memo方式优化
构建函数组件RenderRadio
const RenderRadio = React.memo(({ hostItem, pools, handlePool, service, handleFileSystem, fileSystemList, renderRadioUuid }) => {
return (
<Radio.Group onChange={(e) => handlePool(e, hostItem)} style={{ width: '100%' }}>
{pools[hostItem.id]?.poolList
.map((v) => {
return { ...v, sort: v.system_device || v?.service.includes(service) ? 1 : 0 };
})
.sort((a, b) => b.sort - a.sort)
.map((v) => (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '10px 0' }}>
<Radio disabled={v.system_device || v?.service.includes(service)} value={v.uuid}>
{v.device_node}
{v.system_device ? `(${v.system_device ? t('storage.system-disk') : ''})` : ''}
</Radio>
{v.storage_disk?.fstype ? (
<span style={{ fontSize: '12px', opacity: v.system_device || v?.service.includes(service) ? 0.25 : 1 }}>{v.storage_disk?.fstype}</span>
) : v.visibleFS ? (
<SearchSelect style={{ width: '65px' }} onSelect={(item) => handleFileSystem(hostItem, item)}>
{fileSystemList.map((item) => {
return <Option key={item} value={item}>{`${item}`}</Option>;
})}
</SearchSelect>
) : null}
<span style={{ fontSize: '12px', opacity: v.system_device || v?.service.includes(service) ? 0.25 : 1 }}>{(v.size_mib / 1024).toFixed(2)}G</span>
</div>
))}
</Radio.Group>
);
});
迫不及待的一测试,发现单选无法二次触发RenderRadio
再次渲染,之前是慢现在功能都无法实现。
查看比较逻辑发现默认情况下,React.memo
会进行浅比较(shallow comparison)来检查 props 是否发生变化。如果 props 中的对象是引用类型(比如对象或数组),那么即使对象的内容没有改变,只要引用改变了,React.memo
也会认为 props 发生了变化,从而导致组件重新渲染。
pools[host.id].poolList[index].visibleFS
属性变化无法触发重新渲染,尝试编写自定义比较逻辑,发现最多自定义比较到pools[host.id].poolList
这一层,index未知无法自定义比较下层的visibleFS
属性变化。
手动触发渲染
既然深层数据更新无法在memo函数组件
中触发重新渲染,换种思路来触发。即单选红框区域的磁盘都触发memo函数组件
渲染。业务中定义一个变量renderRadioUuid,单选时重新生成uuid,并作为props传入memo函数组件
,从而触发重新渲染。又迫不及待的测试了一遍渲染速度300ms-,大写的**OK
**。
shouldComponentUpdate尝试
尝试用shouldComponentUpdate优化,遇见和memo中编写自定义比较逻辑同样的问题。
发现最多自定义比较到pools[host.id].poolList
这一层,index未知无法自定义比较下层的visibleFS
属性变化。
遂放弃。
效果疑问 ???
在引入memo高阶组件后,在比较算法上并没有优化多少,只是明确渲染区域,手动控制了渲染频率。按照官方对setState方法的解释,对象数组中对象的某个属性更新只重新渲染那些依赖于这个特定对象属性的组件,其实也是目前memo高阶组件提出来的区域。setState执行也是异步的,不存在频繁渲染造成线程阻塞的问题。推理来说无法解释明显的性能的提升效果。
jym帮忙解释一下小弟的疑问
转载自:https://juejin.cn/post/7352876258332672012