【问题标题】:React Recharts render blocking with a lot of dataReact Recharts 使用大量数据渲染阻塞
【发布时间】:2021-05-25 16:40:31
【问题描述】:

我有一个 Recharts 用例,我渲染了超过 20,000 个数据点,这会导致渲染阻塞:

(CodeSandbox 有一个小的脉冲动画,因此在创建新图表数据时更容易看到阻塞渲染。)

在使用开发工具测量性能时,很明显造成这种情况的原因不是浏览器的paintingrendering 活动,而是Recharts 的scripting 活动:

我不想在这里责怪 Recharts,20k 点很多,但是有没有人知道是否有解决阻塞渲染的方法?

我尝试过的事情:

1.) 增量加载

以增量方式加载更多数据(例如 2k + 2k + 2k + ... = 20k),这只会导致更多、更小的渲染阻塞时间。

2.) 渲染前加载动画

在渲染组件中添加了一个小布尔状态来跟踪“已安装”状态,这至少会在图表组件安装时显示加载动画,因此用户无需等待空白页面/路由切换:

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>
  );
}

【问题讨论】:

    标签: javascript reactjs typescript performance recharts


    【解决方案1】:

    您可以使用(目前处于试验阶段的)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 钩子,每当点击按钮时都使用startTransition 来实现图表数据的无阻塞计算。

    动画不是完美的 60fps,但网站仍然响应!

    查看此代码分支中的差异:

    https://codesandbox.io/s/concurrent-mode-recharts-render-blocking-forked-m62kf?file=/src/App.tsx

    【讨论】:

    • 我知道并发模式,这是一个很好的例子。不幸的是,我目前无法为我的软件使用不稳定的 API,但非常感谢!
    • @Bennett Dams 希望它快点出来!你永远不会知道他们......我会试着想一个更稳定的答案。请注意,即使没有并发模式,我的答案中也有一些优化有助于渲染时间。就像在渲染内部的 data.map 上使用备忘录一样。
    • 您能详细说明一下吗?另外,内联备忘录和渲染之外的备忘录有什么区别?
    • @Bennett Dams useMemo 完全没有关系,它是内联的或存储在变量中的。实际上整个函数组件是一个很大的渲染步骤。我所做的优化是记住从数据到反应组件数组的映射。因此,对于很长的输入,也很有可能使用 useMemo 来记忆计算 JSX 标签。
    猜你喜欢
    • 2018-12-13
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 2014-05-09
    • 2016-01-26
    • 2016-10-26
    • 2017-07-23
    相关资源
    最近更新 更多