Loading...
Loading...
Compare original and translation side by side
$ARGUMENTS$ARGUMENTS// Bad — 매 렌더마다 새 객체 생성
function Parent() {
return <Child style={{ color: "red" }} data={[1, 2, 3]} />;
}
// Good — 안정적 참조
const style = { color: "red" };
const data = [1, 2, 3];
function Parent() {
return <Child style={style} data={data} />;
}// 不良示例 — 每次渲染都会创建新对象
function Parent() {
return <Child style={{ color: "red" }} data={[1, 2, 3]} />;
}
// 良好示例 — 稳定的引用
const style = { color: "red" };
const data = [1, 2, 3];
function Parent() {
return <Child style={style} data={data} />;
}// 비용이 큰 컴포넌트에만 적용
const ExpensiveList = memo(function ExpensiveList({ items }: Props) {
return items.map((item) => <ExpensiveItem key={item.id} item={item} />);
});// 仅对性能开销大的组件使用
const ExpensiveList = memo(function ExpensiveList({ items }: Props) {
return items.map((item) => <ExpensiveItem key={item.id} item={item} />);
});// 비용이 큰 계산에 useMemo
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// memo된 자식에 전달하는 콜백에 useCallback
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);// 对性能开销大的计算使用useMemo
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// 对传递给已缓存子组件的回调使用useCallback
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);// 무거운 컴포넌트 lazy loading
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
loading: () => <Skeleton className="h-[400px]" />,
ssr: false, // 클라이언트 전용
});// 对重型组件进行懒加载
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
loading: () => <Skeleton className="h-[400px]" />,
ssr: false, // 仅客户端渲染
});// Bad — 항상 로드
import { PDFViewer } from "react-pdf";
// Good — 필요할 때만 로드
const PDFViewer = dynamic(() =>
import("react-pdf").then((mod) => ({ default: mod.PDFViewer }))
);// 不良示例 — 始终加载
import { PDFViewer } from "react-pdf";
// 良好示例 — 仅在需要时加载
const PDFViewer = dynamic(() =>
import("react-pdf").then((mod) => ({ default: mod.PDFViewer }))
);// Bad — 전체 라이브러리 import
import _ from "lodash";
_.debounce(fn, 300);
// Good — 개별 함수 import
import debounce from "lodash/debounce";
debounce(fn, 300);
// Best — 작은 대안 사용
import { useDebouncedCallback } from "use-debounce";// 不良示例 — 导入整个库
import _ from "lodash";
_.debounce(fn, 300);
// 良好示例 — 导入单个函数
import debounce from "lodash/debounce";
debounce(fn, 300);
// 最佳方案 — 使用轻量替代库
import { useDebouncedCallback } from "use-debounce";undefinedundefinedundefinedundefined| 무거운 라이브러리 | 가벼운 대안 |
|---|---|
| |
| 개별 import 또는 ES 네이티브 메서드 |
| |
| |
| |
| 重型依赖库 | 轻量替代方案 |
|---|---|
| |
| 单个函数导入或原生ES方法 |
| |
| |
| |
// Next.js Image 컴포넌트 사용
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // LCP 이미지에 priority
placeholder="blur" // 블러 플레이스홀더
sizes="(max-width: 768px) 100vw, 50vw" // 반응형 크기
/>
// 아이콘 — SVG inline 또는 sprite
import { LucideIcon } from "lucide-react"; // 트리쉐이킹 지원// 使用Next.js Image组件
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // 为LCP图片设置priority
placeholder="blur" // 模糊占位符
sizes="(max-width: 768px) 100vw, 50vw" // 响应式尺寸
/>
// 图标 — 使用内联SVG或雪碧图
import { LucideIcon } from "lucide-react"; // 支持Tree Shaking// Server Component에서 데이터 가져오기 (제로 번들)
async function ProductList() {
const products = await db.product.findMany();
return products.map((p) => <ProductCard key={p.id} product={p} />);
}
// 병렬 데이터 페칭
async function Dashboard() {
const [users, orders, stats] = await Promise.all([
getUsers(),
getOrders(),
getStats(),
]);
// ...
}// 在Server Component中获取数据(零打包体积)
async function ProductList() {
const products = await db.product.findMany();
return products.map((p) => <ProductCard key={p.id} product={p} />);
}
// 并行数据获取
async function Dashboard() {
const [users, orders, stats] = await Promise.all([
getUsers(),
getOrders(),
getStats(),
]);
// ...
}// 100+ 아이템 리스트에 가상화 적용
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} className="h-[500px] overflow-auto">
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
height: virtualItem.size,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ListItem item={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}// 对100+条目的列表应用虚拟化
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} className="h-[500px] overflow-auto">
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
height: virtualItem.size,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ListItem item={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}// React Query 캐싱
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5분간 fresh
gcTime: 30 * 60 * 1000, // 30분간 캐시 유지
});
// Next.js fetch 캐싱
const data = await fetch("/api/data", {
next: { revalidate: 3600 }, // 1시간 ISR
});
// Next.js unstable_cache
import { unstable_cache } from "next/cache";
const getCachedData = unstable_cache(
async () => db.query(),
["cache-key"],
{ revalidate: 3600 }
);// React Query缓存
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5分钟内视为新鲜数据
gcTime: 30 * 60 * 1000, // 缓存保留30分钟
});
// Next.js fetch缓存
const data = await fetch("/api/data", {
next: { revalidate: 3600 }, // 1小时重新验证(ISR)
});
// Next.js unstable_cache
import { unstable_cache } from "next/cache";
const getCachedData = unstable_cache(
async () => db.query(),
["cache-key"],
{ revalidate: 3600 }
);| 지표 | 목표 | 최적화 포인트 |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | 히어로 이미지 priority, 폰트 preload |
| FID/INP (Interaction to Next Paint) | < 200ms | 무거운 연산 분리, useDeferredValue |
| CLS (Cumulative Layout Shift) | < 0.1 | 이미지 width/height 지정, 스켈레톤 |
| TTFB (Time to First Byte) | < 800ms | SSG/ISR, 에지 캐싱 |
| 指标 | 目标值 | 优化要点 |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | 英雄图设置priority、预加载字体 |
| FID/INP (Interaction to Next Paint) | < 200ms | 分离重型计算、使用useDeferredValue |
| CLS (Cumulative Layout Shift) | < 0.1 | 为图片指定宽高、使用骨架屏 |
| TTFB (Time to First Byte) | < 800ms | 使用SSG/ISR、边缘缓存 |
undefinedundefinedundefinedundefinedpackage.jsonpackage.json