我有一个 Recharts 用例,我在其中渲染超过 20,000 个数据点,这会导致渲染阻塞:
(CodeSandbox 有一个小的脉冲动画,因此在创建新图表数据时更容易看到阻塞渲染。)
使用开发工具测量性能时,很明显这不是浏览器的 painting
或 rendering
事件,但 Recharts 的 scripting
事件:
我绝对不想在这里责怪 Recharts,20k 点很多,但是有没有人知道是否有办法绕过阻塞渲染?
我试过的东西:
1.) 增量加载
增量加载更多数据(例如 2k + 2k + 2k + ... = 20k),这只会导致更多、更小的渲染阻塞时刻。
2.) 渲染前加载动画
在渲染组件中添加了一个小 bool 状态来跟踪“已安装”状态,它至少会在图表组件安装时显示加载动画,因此用户无需等待空白页面/路由切换:
const [showLoading, setShowLoading] = useState<boolean>(true);
const { isLoading, data } = useXY() // remote data fetching
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
setShowLoading(isLoading || !isMounted);
}, [isLoading, isMounted]);
...
if (showLoading) return <LoadingAnimation />
return <div>...chart...</div>
图表代码:
(完整代码请参见 CodeSandbox)
function Chart({ data }: { data: Data }) {
console.log("⌛ Rendering chart");
const lineData = useMemo(() => {
return data.lines;
}, [data.lines]);
const areaData = useMemo(() => {
return data.areas;
}, [data.areas]);
return (
<ComposedChart
width={500}
height={400}
margin={{
top: 20,
right: 20,
bottom: 20,
left: 20
}}
>
<CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="ts" type="number" />
<YAxis />
<Tooltip />
{areaData.map((area) => (
<Area
// @ts-ignore
data={area.data}
dataKey="value"
isAnimationActive={false}
key={area.id}
type="monotone"
fill="#8884d8"
stroke="#8884d8"
/>
))}
{lineData.map((line) => (
<Line
data={line.data}
dataKey="value"
isAnimationActive={false}
key={line.id}
type="monotone"
stroke="#ff7300"
/>
))}
</ComposedChart>
);
}
最佳答案
你可以使用(当前实验的)React Concurrent Mode .
在并发模式下,渲染是非阻塞的。
export default function App() {
// imagine data coming from an async request
const [data, setData] = useState<Data>(() => createData());
const [startTransition, isPending] = unstable_useTransition();
function handleNoneBlockingClick() {
startTransition(() => setData(createData()));
}
function handleBlockingClick() {
setData(createData());
}
return (
<div className="App">
<button onClick={handleNoneBlockingClick}>
(None blocking) Regenerate data
</button>
<button onClick={handleBlockingClick}>(Blocking) Regenerate data</button>
{isPending && <div>...pending</div>}
{data && (
<>
<p>
Number of data points to render:{" "}
{useMemo(
() =>
data.lines.reduce((acc, item) => {
return acc + item.data.length;
}, 0),
[data.lines]
) +
useMemo(
() =>
data.areas.reduce((acc, item) => {
return acc + item.data.length;
}, 0),
[data.areas]
)}
</p>
<Animation />
<Chart data={data} />
</>
)}
</div>
);
}
在这个例子中,我使用了新的unstable_useTransition 钩子(Hook),并在单击按钮时使用startTransition 来实现图表数据的无阻塞计算。动画不是完美的 60fps,但网站仍然响应!
查看此代码分支中的差异:
https://codesandbox.io/s/concurrent-mode-recharts-render-blocking-forked-m62kf?file=/src/App.tsx
关于javascript - React Recharts 使用大量数据渲染阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66332632/