背景
在table上面,当鼠标放在cell上面的时候,需要去请求接口拉取数据,然后setList(res.result)
后,希望render中的traceIds也能够实时更新渲染。
const [traceIds, setTraceIds] = useState() // 需要展示在popover上面的数据,接口返回
const renderErrorCodeList = useMemoizedFn((errors: MttkError[], record: MttkInfo) => {
return (
<ul className="table-error-code-ul">
{errors?.length
? errors?.map((err) => {
const msg = err.error_message?.map((e) => e.message)?.join(';');
return (
<li key={err.code}>
<Popover
placement="top"
content={{loading?'loading...':traceIds}} // 关键点,当list改变的时候,这里的popover并不会重新渲染
trigger="hover"
title="Detail"
overlayClassName="trace-detail-popover"
overlayStyle={{ width: 500 }}
>
<a
onMouseEnter={() => {
// 避免鼠标来回滑入滑出,造成接口的调用
// 希望用户能够将鼠标停留在链接上面之后,再唤起接口和popover
timer.current = setTimeout(() => {
// 获取接口数据...
setTraceIds(res.data);
}, 1000);
}}
>
{`${err.code}${msg ? `(${msg})` : ''}`}
</a>
</Popover>
</li>
);
})
: '-'}
</ul>
);
});
const columns = [
{
title: 'Top Error Codes',
dataIndex: 'top_error',
width: 500,
render: (errors: MttkError[], record: MttkInfo) => renderErrorCodeList(errors, record),
},
{
title: 'QPS',
dataIndex: 'qps',
},
];
<Table
columns={columns}
dataSource={data}
size="small"
/>
原因:state改变并不会触发render重新渲染。
参考:https://github.com/ant-design/ant-design/issues/35820、
https://github.com/ant-design/ant-design/issues/418
解决方法
(1)考虑前端自定义一个变量list,当接口返回数据后,我们手动修改datasource的数据
(2)利用 shouldCellUpdate
实时更新当前单元格
export default function InfoTable(props: Props) {
const { dataSource, timestamp, azKey } = props;
const timer = useRef<number>();
const [data, setData] = useState<MttkInfo[]>([]); // datasource
const jumpToTracingPage = (traceId: string) => {
window.open(`${basePath}/tracing?traceId=${traceId}`);
};
const { run: getTraceList } = useDebounceFn(
async (code: string, record: MttkInfo, latency?: number) => {
try {
// 请求接口...
const res = await getTraceIdListByCode(params);
const newData = data?.map((info) => {
if (info.id === record.id) {
return { ...info, traceIds: res?.data, isCallEnd: Math.random() }; // 关键点
}
// 对于没有命中的record,重置traceIds,方便判断是否展示 loading
return { ...info, traceIds: undefined };
});
setData(newData);
}
} catch (err) {
console.error(err);
}
},
{
wait: 500,
},
);
const getErrorInfo = (error: MttkError) => {
return <div>其他固定信息...</div>
};
const renderTraceListContent = (traceIds?: string[]) => {
let content: ReactNode = '';
// 关键点:判断状态
if (traceIds === undefined) {
content = 'Loading...';
} else if (traceIds?.length === 0) {
content = 'Not found';
} else {
content = (
<ul style={{ paddingLeft: 15 }}>
{traceIds?.map((id) => (
<li key={id}>
<a onClick={() => jumpToTracingPage(id)}>{id}</a>
</li>
))}
</ul>
);
}
return content;
};
const renderErrorCodeList = useMemoizedFn((errors: MttkError[], record: MttkInfo) => {
const { traceIds = undefined } = record;
const content = renderTraceListContent(traceIds); // 获取trace id列表
return (
<ul className="table-error-code-ul">
{errors?.length
? errors?.map((err) => {
const baseInfo = getErrorInfo(err);
const msg = err.error_message?.map((e) => e.message)?.join(';');
return (
<li key={err.code}>
<Popover
placement="top"
content={
<>
{baseInfo}
<div style={{ fontSize: 12 }}>{content}</div>
</>
}
trigger="hover"
title="Detail"
overlayClassName="trace-detail-popover"
overlayStyle={{ width: 500 }}
>
<a
onMouseEnter={() => {
// 避免鼠标来回滑入滑出,造成接口的调用
// 希望用户能够将鼠标停留在链接上面之后,再唤起接口和popover
timer.current = setTimeout(() => {
getTraceList(err.code, record);
}, 1000);
}}
onMouseLeave={() => {
clearTimeout(timer.current);
// 对于同一个单元格,如果有多个code,重复点击同一个单元格的时候,会造成一直显示上次的内容。所以在鼠标移出后,需要重置traceIds为undefined
// 或者同一行来回移动,popover还是会保持上一次的内容
setData(data?.map((info) => ({ ...info, traceIds: undefined })));
}}
>
{`${err.code}${msg ? `(${msg})` : ''}`}
</a>
</Popover>
</li>
);
})
: '-'}
</ul>
);
});
const columns = [
{
title: 'Top Error Codes',
dataIndex: 'top_error',
width: 500,
render: (errors: MttkError[], record: MttkInfo) => renderErrorCodeList(errors, record),
shouldCellUpdate: (record: MttkInfo, pre: MttkInfo) => record?.isCallEnd !== pre?.isCallEnd, // 关键点,表示请求结束了,需要重新渲染当前单元格
},
{
title: 'QPS',
dataIndex: 'qps',
},
];
return (
<Table
columns={columns}
dataSource={data}
size="small"
/>
);
}
但是上面的处理方法存在一个问题:
(1)如果在同一行来回移动,popover展示的是上一次的信息
(2)如果在当前单元格内上下移动(比如有多个li元素),popover展示的是上一次的信息
希望能够找到优化方式…
或者考虑使用 useUpdate hooks,在需要setState重新渲染的时候,调一下它??但是会造成表格的所有数据都被重新渲染