多标签页面和@antv/g2 结合使用的 bug
分析原因
由于 react 官方没有像 vue 中 keep:alive 的多标签页解决方案;
实际工作中我们选取了react-router-cache-route
该方案会将数据保存一份并使用 display:none 和 display:block 的形式对多 tab 进行切换;
但当我们使用 @antv/g2
对一些数据进行图片渲染时由于异步数据且 tab 页面会存储 之前的 state,
导致 tab 页面的图表 render 时出现多次渲染,在某一个 container 中渲染得到了多个图表 canvas 的结果;
Chart 使用
- 处理数据
- 实例化 Chart
- 填充数据
- 绘制图标横纵轴等信息
- 绘制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| const renderChat = () => { const data = dataDetailList.map((item: IDataDetailList) => ({ type: item.sub_tag_name, value: item.num })); const chart = new Chart({ container: 'container', autoFit: true, height: 500, }); chart.data(data); chart.scale({ value: { max: 20, min: 0, alias: '标签(个数)', }, }); chart.axis('type', { title: null, tickLine: null, line: null, });
chart.axis('value', { title: { offset: 30, style: { fontSize: 12, fontWeight: 300, }, }, tickLine: { length: 5, }, subTickLine: { length: 5, count: 1, }, grid: { }, }); chart.legend(false); chart.coordinate().transpose(); chart .interval() .position('type*value') .size(26) .label('value', { style: { fill: '#8d8d8d', }, offset: 10, }); chart.interaction('element-active'); chart.render(); };
|
问题处理方案构思
容器多样化
在实例化容器时我们传入了container: 'container'
;
这里其实是给 Chart 一个填充的对象 id;
因此猜测由于多页面都是使用同一个组件 使用了同一个 id 的容器进行了渲染填充才导致的过个图标的出现;
结果新进入的标签页没有渲染而第一次打开的标签页却渲染了多次
审查元素们发现 第一个开启的标签页中共被填充了多次这里我开启了三个标签页,因此出现了三个存储 canvas 的 div
既然如此我们将 query 参数取出当做 container 的变量实例化时不就区分了么
结果发现虽然 container 的 id 变了但是第一次打开的标签页的 container 还是被 render 插入了多个 canvas
至此第一个方案失败
可能的原因: react-router-cache-route
的复用机制虽然是使用不同的 dom 节点但不同的 dom 却是使用同一个组件形成的
因此后续 tab 页的图标都被渲染到了首次加载的 tab 页面
ref
经过容器使用 id 进行区分的挫败,我们可以想到直接使用 dom 呢?
首先得验证是否支持
果然 container 也是允许我们传入 dom 节点的那就愉快的使用 useRef()了;
确实如我们所预料的基本每个 tab 也都能正常渲染了 但还是会出现以上的情况
定睛一看,这不就是多了上次打开的 tab 也的数据么
说明虽然数据渲染了但是上次的 tab 页面的 render 函数也生效进行了 canvas 的插入
因此我们只需要在每次 render 时将 chart 实例存储起来,然后在 render 之前将 chart 实例 destroy 掉
应该就行了吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| import { Chart } from '@antv/g2'; import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { getQueryString } from 'utils/utils';
import selectors from '../selectors'; import { IDataDetailList } from '../types';
function ChartComponent(): JSX.Element { const dataDetailList = useSelector(selectors.dataDetailList); const queryId = getQueryString('id');
const chartObj = React.useRef(null); const divEle = React.useRef(null);
const renderChat = () => { const data = dataDetailList.map((item: IDataDetailList) => ({ type: item.sub_tag_name, value: item.num })); const chart = new Chart({ container: divEle.current, autoFit: true, height: 500, }); chart.data(data); chart.scale({ value: { max: 20, min: 0, alias: '标签(个数)', }, }); chart.axis('type', { title: null, tickLine: null, line: null, });
chart.axis('value', { title: { offset: 30, style: { fontSize: 12, fontWeight: 300, }, }, tickLine: { length: 5, }, subTickLine: { length: 5, count: 1, }, grid: { }, }); chart.legend(false); chart.coordinate().transpose(); chart .interval() .position('type*value') .size(26) .label('value', { style: { fill: '#8d8d8d', }, offset: 10, }); chart.interaction('element-active'); chart.render(); chartObj.current = chart; };
useEffect(() => { if (dataDetailList.length) { renderChat(); } }, [JSON.stringify(dataDetailList)]);
return <div ref={divEle} key={queryId} />; }
export default ChartComponent;
|
至此我们也得到了最终的方法并且成功处理了 tab 页面之间的影响