最近因为项目要求,开始学习并且使用React和Ant Design框架。在前端开发过程中,遇到这样一个页面:有多个tab,每个tab下都是一个table表格来进行数据展示。但,每个table的title都是一样的,如何进行表格的渲染和对应数据的展示就值得开发人员考虑。下面,我们一起来瞧瞧。
在开发中,最大程度上对性能进行优化,是开发人员的不懈追求。对于前端开发而言,操作尽可能少的DOM的,是页面性能优化的一个重要部分。
为什么这样说呢,来看看下面这个简化版的,浏览器输入url到网页显示的整个过程就明白了:
“页面渲染呈现给用户”这最后一步,又是这样一个过程:
那么,既然每个table的title都一样,只是渲染的数据有不同,基于性能优化的考虑,我让这个页面的每个tab都共用一个table,每次点击不同的tab,根据此tab的key值,调用不同的后端数据接口,处理数据并完成数据更新。
这样,最大程度的减少了DOM的渲染,达到性能优化的目的。能推出,这个table表格越复杂,优化的效果越好,因为减少了更多重复的DOM渲染(这是不必要的)。
既然已经从性能优化角度分析除了最好的方式是减少DOM渲染,那么,最直接的方法就是,在react的对应组件中(主要使用了antd的Tabs、Table组件),直接让Table组件和Tabs组件平级即可。为了更清楚,直接上代码:
// 我用的是函数组件
import { ..., Table, Tabs, ... } from 'antd';
import React, { useEffect, ..., useState } from 'react';
...
const onChange = (key: string) => {
if (key === '1') {
// 这里写调用接口和处理数据的逻辑代码
...
}else if key === ‘2’) {
// 这里写调用接口和处理数据的逻辑代码
...
}...
}
...
const App: React.FC = () => {
...
return (
// 让Table组件和Tabs组件平级(这里的结构是关键)
<>
...
...
>
)
}
大的思路想好之后,代码开发过程中,我在更新Table的datasource过程中又遇到了问题。预想的是,进入到这个页面时,默认选中第一个tab,调第一个tab的数据接口,然后将数据赋值给Table的datasource,让表格对其进行展示。
1、写在useEffect中的接口调用,通过浏览器控制台的Network,看到这个接口在不断的调用,导致点击别的tab,数据在瞬间更新后又被useEffect钩子中的第一个tab数据冲掉;
2、datasource数据赋值成功了,但是Table表格中数据却没有更新。
问题1:useEffect的函数在不断地被执行,导致同一个接口被不断的调用,我第一反应就是去查阅useEffect钩子的说明文档,果然被我找到了原因,下面贴几张官方文档的截图来说明:
下面这张图是最主要的原因
问题2:Table的dataSource 更新,Table未重新渲染。为什么React不重新渲染DOM呢?从这个思路出发,在查阅了React的DOM的重新渲染及之后,在看我的给datasource赋值代码,(才开始,我没有用useState钩子函数,而是直接定义了一个空数组变量给datasource使用)发现了问题:
// 这是我有问题的赋值代码:
...
const tab2Datasource = async () => {
const apiRes = await 接口调用函数名;
if (apiRes) {
// 假设这块的apiRes已经被我处理成我需要的数据格式了
// 注意这块的赋值代码,我是直接“=”赋值的,对于数组这种复杂数据类型来说,这只是改变了它在栈内存中的指针,并没有实质性的改变其堆内存中的数据。
// 但就是因为指向了一个地址,React认为虚拟DOM并没有改变,因此,不会重新渲染页面。
// 这就会导致Table组件的dataSource改变了,但是Table并没有重新渲染
tableData = apiRes
}
}
...
再分别找出bug的原因之后,修改起来就很简单了。直接上代码:
问题1:解决接口函数在useEffect钩子中被无限调用的问题
// 这是原来的bug代码:
useEffect(() => { tab1Datasource() });
// 给useEffect钩子中加上空数组的参数即可
useEffect(() => { GetMyToDoFlowList() }, []);
问题2:解决datasource数据更新了但Table不更新的问题
// 原来的bug代码
见“3.2、问题分析”的代码
// 成功修改Bug后的代码
...
import React, { useEffect, ..., useState } from 'react';
...
const App: React.FC = () => {
...
// 1、使用useState钩子函数
const [tableData, setTableData] = useState(Array);
// 2、重新改变数组的赋值方式
// 解决原理也很简单,新建一个Array指向不同地址,再赋值,这样React就会认为需要重新渲染Table了
const tab2Datasource = async () => {
const apiRes = await 接口调用函数名;
if (apiRes) {
const tableDatas: DataType[] = [];
const datas: DataType[] = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
...
];
datas.forEach((item) => {
tableDatas.push(item);
});
setTableData(tableDatas);
} else {
console.log('代办工作数据请求失败!!!');
}
};
}
参考链接:
React官方文档之使用 Effect Hook
JS的浅拷贝和深拷贝的剖析以及部分实现方法
React Table dataSource 更新,Table未重新渲染
欢迎大家一起讨论、学习