Loading...
Loading...
Compare original and translation side by side
pnpm run build
pnpm run previewpnpm run build
pnpm run previewsrc/App.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} />// BAD
<Button onClick={() => doSomething(id)} />
// GOOD
const handleClick = useCallback(() => doSomething(id), [id]);
<Button onClick={handleClick} />// 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}>grep -rn --include="*.tsx" \
-E "value=\{\{|onClick=\{\(\)" src/React.memosrc/App.tsx// 不良写法——每次渲染都会创建新对象,导致子组件重渲染
<Chart options={{ color: "red" }} />
// 良好写法
const chartOptions = useMemo(() => ({ color: "red" }), []);
<Chart options={chartOptions} />// 不良写法
<Button onClick={() => doSomething(id)} />
// 良好写法
const handleClick = useCallback(() => doSomething(id), [id]);
<Button onClick={handleClick} />// 不良写法——每次渲染都会创建新的对象引用
<MyContext.Provider value={{ user, sdk }}>
// 良好写法——对Context值进行记忆化处理
const ctxValue = useMemo(() => ({ user, sdk }), [user, sdk]);
<MyContext.Provider value={ctxValue}>grep -rn --include="*.tsx" \
-E "value=\{\{|onClick=\{\(\)" src/React.memosdk.client.useQueryuseCogniteClient| Issue | Fix |
|---|---|
No | Add |
| Fetching all properties | Add a |
| Fetching on every render | Move inside |
| Sequential requests that could be parallel | Use |
| Polling without debounce | Add debounce (300–500 ms) for search inputs before firing CDF queries |
// 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,
});sdk.client.useQueryuseCogniteClient| 问题 | 修复方案 |
|---|---|
未设置 | 添加 |
| 获取所有属性 | 添加 |
| 每次渲染都进行获取 | 将调用移至 |
| 可并行执行的串行请求 | 使用 |
| 无防抖的轮询 | 为搜索输入添加防抖(300–500毫秒)后再触发CDF查询 |
// 不良写法——获取所有数据
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,
});grep -rn --include="*.tsx" -E "\.(map|forEach)\(" src/.map()@tanstack/react-virtualimport { 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>
);grep -rn --include="*.tsx" -E "\.(map|forEach)\(" src/.map()@tanstack/react-virtualimport { 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>
);// 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>// 优化前
import { ReportPage } from "./pages/ReportPage";
// 优化后
import { lazy, Suspense } from "react";
const ReportPage = lazy(() => import("./pages/ReportPage"));
// 在路由中使用:
<Suspense fallback={<PageSkeleton />}>
<ReportPage />
</Suspense>undefinedundefined
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 }),
],
});pnpm run buildlodashlodash-esmomentdate-fnsIntl
临时添加到`vite.config.ts`中:
```ts
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
react(),
visualizer({ open: true, gzipSize: true, brotliSize: true }),
],
});pnpm run buildlodashlodash-esmomentdate-fnsIntluseEffectgrep -rn --include="*.tsx" --include="*.ts" -A 10 "useEffect" src/useEffectaddEventListenersetIntervalsetTimeoutsubscribeuseEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort();
}, [id]);useEffectgrep -rn --include="*.tsx" --include="*.ts" -A 10 "useEffect" src/addEventListenersetIntervalsetTimeoutsubscribeuseEffectuseEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort();
}, [id]);| Metric | Before | After | Change |
|---|---|---|---|
| Lighthouse Performance | 72 | 91 | +19 |
| Largest Contentful Paint | 3.2 s | 1.8 s | −1.4 s |
| Total Blocking Time | 420 ms | 80 ms | −340 ms |
| Bundle size (gzipped) | 410 KB | 290 KB | −120 KB |
| 8 | 2 | −6 |
| 指标 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| Lighthouse性能评分 | 72 | 91 | +19 |
| 最大内容绘制 | 3.2 s | 1.8 s | −1.4 s |
| 总阻塞时间 | 420 ms | 80 ms | −340 ms |
| 包体积(gzip压缩后) | 410 KB | 290 KB | −120 KB |
| 8 | 2 | −6 |