performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Performance Optimization

性能优化

Systematically identify and fix performance issues in $ARGUMENTS (or the whole app if no argument is given). Always measure first — never optimize blindly.

系统性地识别并修复**$ARGUMENTS**中的性能问题(若未指定参数则针对整个应用)。务必先进行测量——切勿盲目优化。

Step 1 — Measure baseline before touching anything

步骤1——修改前先测量基准值

Run the production build and capture metrics before making any changes:
bash
pnpm run build
pnpm run preview
Open the app in Chrome and capture:
  • Lighthouse score (Performance tab → Run audit)
  • React Profiler (React DevTools → Profiler → Record an interaction)
    • Note the components with the longest render times and highest render counts
Record baseline numbers. Every optimization must be measured against these.

运行生产构建并在做出任何更改前捕获指标:
bash
pnpm run build
pnpm run preview
在Chrome中打开应用并捕获:
  • Lighthouse评分(性能标签页 → 运行审核)
  • React Profiler(React DevTools → Profiler → 记录交互)
    • 记录渲染时间最长和渲染次数最多的组件
记录基准数值。所有优化都必须以此为参照进行衡量。

Step 2 — Identify unnecessary re-renders

步骤2——识别不必要的重渲染

Read the component tree (start from
src/App.tsx
) and look for these patterns:
Inline object/array/function creation in JSX:
tsx
// BAD — new object on every render causes children to re-render
<Chart options={{ color: "red" }} />

// GOOD
const chartOptions = useMemo(() => ({ color: "red" }), []);
<Chart options={chartOptions} />
Event handlers recreated on every render:
tsx
// BAD
<Button onClick={() => doSomething(id)} />

// GOOD
const handleClick = useCallback(() => doSomething(id), [id]);
<Button onClick={handleClick} />
Context that changes on every render:
tsx
// BAD — new object reference every render
<MyContext.Provider value={{ user, sdk }}>

// GOOD — memoize the context value
const ctxValue = useMemo(() => ({ user, sdk }), [user, sdk]);
<MyContext.Provider value={ctxValue}>
Search for common culprits:
bash
grep -rn --include="*.tsx" \
  -E "value=\{\{|onClick=\{\(\)" src/
Apply
React.memo
to pure presentational components that receive stable props. Do NOT wrap every component — only those confirmed to re-render unnecessarily via the Profiler.

查看组件树(从
src/App.tsx
开始),寻找以下模式:
JSX中内联创建对象/数组/函数:
tsx
// 不良写法——每次渲染都会创建新对象,导致子组件重渲染
<Chart options={{ color: "red" }} />

// 良好写法
const chartOptions = useMemo(() => ({ color: "red" }), []);
<Chart options={chartOptions} />
每次渲染都会重新创建事件处理函数:
tsx
// 不良写法
<Button onClick={() => doSomething(id)} />

// 良好写法
const handleClick = useCallback(() => doSomething(id), [id]);
<Button onClick={handleClick} />
每次渲染都会变化的Context:
tsx
// 不良写法——每次渲染都会创建新的对象引用
<MyContext.Provider value={{ user, sdk }}>

// 良好写法——对Context值进行记忆化处理
const ctxValue = useMemo(() => ({ user, sdk }), [user, sdk]);
<MyContext.Provider value={ctxValue}>
搜索常见问题代码:
bash
grep -rn --include="*.tsx" \
  -E "value=\{\{|onClick=\{\(\)" src/
为接收稳定props的纯展示组件应用
React.memo
。请勿包裹所有组件——仅包裹那些经Profiler确认存在不必要重渲染的组件。

Step 3 — Optimize CDF data fetching

步骤3——优化CDF数据获取

Read all CDF SDK calls (search for
sdk.
,
client.
,
useQuery
,
useCogniteClient
).
For each call, check:
IssueFix
No
limit
set
Add
limit: 100
(or the actual page size needed)
Fetching all propertiesAdd a
properties
filter to select only required fields
Fetching on every renderMove inside
useQuery
/
useMemo
with a stable dependency array
Sequential requests that could be parallelUse
Promise.all
or batched SDK methods
Polling without debounceAdd debounce (300–500 ms) for search inputs before firing CDF queries
Example — scoped instance query:
ts
// BAD — fetches everything
const result = await client.instances.list({ instanceType: "node" });

// GOOD — scoped and limited
const result = await client.instances.list({
  instanceType: "node",
  sources: [{ source: { type: "view", space: "my-space", externalId: "Asset", version: "v1" } }],
  filter: { equals: { property: ["node", "space"], value: "my-space" } },
  limit: 100,
});

查看所有CDF SDK调用(搜索
sdk.
client.
useQuery
useCogniteClient
)。
针对每个调用,检查以下内容:
问题修复方案
未设置
limit
添加
limit: 100
(或所需的实际分页大小)
获取所有属性添加
properties
过滤器,仅选择所需字段
每次渲染都进行获取将调用移至
useQuery
/
useMemo
中,并使用稳定的依赖数组
可并行执行的串行请求使用
Promise.all
或批量SDK方法
无防抖的轮询为搜索输入添加防抖(300–500毫秒)后再触发CDF查询
示例——限定范围的实例查询:
ts
// 不良写法——获取所有数据
const result = await client.instances.list({ instanceType: "node" });

// 良好写法——限定范围并设置数量限制
const result = await client.instances.list({
  instanceType: "node",
  sources: [{ source: { type: "view", space: "my-space", externalId: "Asset", version: "v1" } }],
  filter: { equals: { property: ["node", "space"], value: "my-space" } },
  limit: 100,
});

Step 4 — Virtualize large lists

步骤4——虚拟化大型列表

Search for lists that render more than ~50 items:
bash
grep -rn --include="*.tsx" -E "\.(map|forEach)\(" src/
For any list where the data source could exceed 50 items, replace the plain
.map()
render with a virtualized list. Use
@tanstack/react-virtual
(already a Dune ecosystem dependency):
tsx
import { useVirtualizer } from "@tanstack/react-virtual";

const rowVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 48,
});

return (
  <div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
    <div style={{ height: rowVirtualizer.getTotalSize() }}>
      {rowVirtualizer.getVirtualItems().map((virtualRow) => (
        <div
          key={virtualRow.key}
          style={{ transform: `translateY(${virtualRow.start}px)` }}
        >
          {items[virtualRow.index].name}
        </div>
      ))}
    </div>
  </div>
);

搜索渲染超过约50条数据的列表:
bash
grep -rn --include="*.tsx" -E "\.(map|forEach)\(" src/
对于数据源可能超过50条的列表,将普通的
.map()
渲染替换为虚拟化列表。使用
@tanstack/react-virtual
(已作为Dune生态系统依赖):
tsx
import { useVirtualizer } from "@tanstack/react-virtual";

const rowVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 48,
});

return (
  <div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
    <div style={{ height: rowVirtualizer.getTotalSize() }}>
      {rowVirtualizer.getVirtualItems().map((virtualRow) => (
        <div
          key={virtualRow.key}
          style={{ transform: `translateY(${virtualRow.start}px)` }}
        >
          {items[virtualRow.index].name}
        </div>
      ))}
    </div>
  </div>
);

Step 5 — Code-split heavy pages and components

步骤5——代码分割重型页面和组件

Any page or modal that is not needed on the initial load should be lazy-loaded. Read the router setup and identify routes that are imported statically but not shown on the landing page.
tsx
// BEFORE
import { ReportPage } from "./pages/ReportPage";

// AFTER
import { lazy, Suspense } from "react";
const ReportPage = lazy(() => import("./pages/ReportPage"));

// In the route:
<Suspense fallback={<PageSkeleton />}>
  <ReportPage />
</Suspense>
Similarly, large third-party components (chart libraries, PDF viewers, map renderers) should be dynamically imported inside the component that needs them, not at the module level.

任何初始加载时不需要的页面或模态框都应懒加载。查看路由配置,识别那些被静态导入但不在首页显示的路由。
tsx
// 优化前
import { ReportPage } from "./pages/ReportPage";

// 优化后
import { lazy, Suspense } from "react";
const ReportPage = lazy(() => import("./pages/ReportPage"));

// 在路由中使用:
<Suspense fallback={<PageSkeleton />}>
  <ReportPage />
</Suspense>
同样,大型第三方组件(图表库、PDF查看器、地图渲染器)应在需要它们的组件内部动态导入,而非在模块级别导入。

Step 6 — Analyse and reduce bundle size

步骤6——分析并缩小包体积

bash
undefined
bash
undefined

Install if not already present, then run

若尚未安装则先安装,然后运行

pnpm add -D rollup-plugin-visualizer

Add to `vite.config.ts` temporarily:
```ts
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  plugins: [
    react(),
    visualizer({ open: true, gzipSize: true, brotliSize: true }),
  ],
});
Run
pnpm run build
and inspect the treemap. Flag any chunk > 100 KB (gzipped) that is not a necessary initial dependency.
Common wins:
  • Replace
    lodash
    with individual
    lodash-es
    imports or native equivalents
  • Replace
    moment
    with
    date-fns
    or native
    Intl
  • Ensure chart libraries (e.g., ECharts, Recharts) are tree-shaken correctly
Revert the visualizer plugin after analysis.

pnpm add -D rollup-plugin-visualizer

临时添加到`vite.config.ts`中:
```ts
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  plugins: [
    react(),
    visualizer({ open: true, gzipSize: true, brotliSize: true }),
  ],
});
运行
pnpm run build
并查看树形图。标记任何大于100 KB(gzip压缩后)且非必要初始依赖的代码块。
常见优化点:
  • lodash
    替换为单独的
    lodash-es
    导入或原生等效方法
  • moment
    替换为
    date-fns
    或原生
    Intl
  • 确保图表库(如ECharts、Recharts)被正确摇树优化
分析完成后移除visualizer插件。

Step 7 — Fix memory leaks

步骤7——修复内存泄漏

Search for
useEffect
hooks that set up subscriptions, timers, or event listeners without cleanup:
bash
grep -rn --include="*.tsx" --include="*.ts" -A 10 "useEffect" src/
Every
useEffect
that calls
addEventListener
,
setInterval
,
setTimeout
,
subscribe
, or sets up a CDF streaming connection must return a cleanup function:
ts
useEffect(() => {
  const controller = new AbortController();
  fetchData(controller.signal);
  return () => controller.abort();
}, [id]);

搜索
useEffect
钩子中设置了订阅、定时器或事件监听器但未清理的代码:
bash
grep -rn --include="*.tsx" --include="*.ts" -A 10 "useEffect" src/
所有调用
addEventListener
setInterval
setTimeout
subscribe
或建立CDF流式连接的
useEffect
都必须返回清理函数:
ts
useEffect(() => {
  const controller = new AbortController();
  fetchData(controller.signal);
  return () => controller.abort();
}, [id]);

Step 8 — Measure after and report

步骤8——优化后测量并报告

Re-run the same Lighthouse audit and React Profiler session from Step 1. Report the delta:
MetricBeforeAfterChange
Lighthouse Performance7291+19
Largest Contentful Paint3.2 s1.8 s−1.4 s
Total Blocking Time420 ms80 ms−340 ms
Bundle size (gzipped)410 KB290 KB−120 KB
AssetTable
render count (on filter change)
82−6
If a step produced no improvement, state that explicitly. Do not fabricate numbers.

重新运行步骤1中的Lighthouse审核和React Profiler会话。报告差值:
指标优化前优化后变化
Lighthouse性能评分7291+19
最大内容绘制3.2 s1.8 s−1.4 s
总阻塞时间420 ms80 ms−340 ms
包体积(gzip压缩后)410 KB290 KB−120 KB
AssetTable
渲染次数(筛选变更时)
82−6
若某一步骤未带来提升,请明确说明。请勿编造数据。

Done

完成

List every change made with the file path and a one-line explanation. If further gains require server-side or infrastructure changes (e.g., CDF response caching, CDN configuration), note them separately as out-of-scope recommendations.
列出所有已做的更改,包含文件路径和一行说明。若进一步优化需要服务端或基础设施变更(如CDF响应缓存、CDN配置),请单独列出这些超出范围的建议。