Instruction file imported from wjgogogo/cursor-rules (
.cursor/rules/performance-optimization.mdc). Copyright stays with the author.
description: globs: .js,.jsx,.ts,.tsx alwaysApply: false
性能优化规范
核心指标监控 [P1]
【必须】监控 Web Vitals 核心指标:
import { getCLS, getFID, getLCP } from 'web-vitals';
// ✅ 基础性能监控
function sendToAnalytics(metric: any) {
fetch('/api/performance', {
method: 'POST',
body: JSON.stringify({
name: metric.name,
value: metric.value,
url: window.location.href,
}),
});
}
// 监控核心指标
getCLS(sendToAnalytics); // 累积布局偏移 < 0.1
getFID(sendToAnalytics); // 首次输入延迟 < 100ms
getLCP(sendToAnalytics); // 最大内容绘制 < 2.5s
图片优化
【必须】优化图片加载:
// ✅ 正确 - 图片懒加载
function LazyImage({ src, alt, ...props }: ImageProps) {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={isLoaded ? src : undefined}
alt={alt}
loading="lazy"
{...props}
/>
);
}
// ✅ 正确 - 响应式图片格式
function ResponsiveImage({ src, alt }: { src: string; alt: string }) {
return (
<picture>
<source srcSet={`${src}.webp`} type="image/webp" />
<img src={`${src}.jpg`} alt={alt} loading="lazy" />
</picture>
);
}
代码分割
【必须】按需加载组件:
import { lazy, Suspense } from 'react';
// ✅ 正确 - 路由级分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// ✅ 正确 - 组件级分割
const HeavyChart = lazy(() => import('./HeavyChart'));
function DashboardPage() {
return (
<div>
<h1>仪表板</h1>
<Suspense fallback={<div>加载图表...</div>}>
<HeavyChart />
</Suspense>
</div>
);
}
Bundle 优化
【必须】优化打包体积:
// ✅ 正确 - 按需导入
import { debounce, throttle } from 'lodash-es';
import { format, parseISO } from 'date-fns';
// ❌ 错误 - 导入整个库
import _ from 'lodash';
import moment from 'moment';
// ✅ 正确 - 动态导入重型库
async function loadChart() {
const { Chart } = await import('chart.js');
return Chart;
}
// ✅ 正确 - Tree Shaking 配置
// package.json
{
"sideEffects": false, // 启用 tree shaking
"type": "module"
}
React 渲染优化
【必须】合理使用 React 优化:
// ✅ 正确 - React.memo
const UserCard = React.memo(function UserCard({ user, onEdit }: UserCardProps) {
return (
<div>
<h3>{user.name}</h3>
<button onClick={() => onEdit(user)}>编辑</button>
</div>
);
});
// ✅ 正确 - useMemo 缓存计算结果
function UserList({ users }: { users: User[] }) {
const sortedUsers = useMemo(() => {
return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
return (
<div>
{sortedUsers.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
// ✅ 正确 - useCallback 缓存函数
function TodoList({ todos, onToggle }: TodoListProps) {
const handleToggle = useCallback((id: string) => {
onToggle(id);
}, [onToggle]);
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</div>
);
}
用户交互优化
【必须】实现防抖和节流:
// ✅ 正确 - 防抖 Hook
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// ✅ 正确 - 搜索防抖
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
onSearch(debouncedQuery);
}
}, [debouncedQuery, onSearch]);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
);
}
// ✅ 正确 - 节流 Hook
function useThrottle<T>(value: T, delay: number): T {
const [throttledValue, setThrottledValue] = useState<T>(value);
const lastRan = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRan.current >= delay) {
setThrottledValue(value);
lastRan.current = Date.now();
}
}, delay - (Date.now() - lastRan.current));
return () => clearTimeout(handler);
}, [value, delay]);
return throttledValue;
}
缓存策略
【推荐】实现基础缓存:
// ✅ 正确 - React Query 缓存
import { useQuery } from '@tanstack/react-query';
function useUserData(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5分钟缓存
cacheTime: 10 * 60 * 1000, // 10分钟后清除
});
}
// ✅ 正确 - 内存缓存
const cache = new Map<string, { data: any; timestamp: number }>();
function getCachedData(key: string, ttl: number = 300000) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
return null;
}
function setCachedData(key: string, data: any) {
cache.set(key, { data, timestamp: Date.now() });
}
内存泄漏防护
【必须】清理副作用:
// ✅ 正确 - 清理事件监听器
function WindowSizeTracker() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>{size.width} x {size.height}</div>;
}
// ✅ 正确 - 清理定时器
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>计数: {count}</div>;
}
虚拟滚动
【推荐】处理大量数据:
import { FixedSizeList as List } from 'react-window';
// ✅ 正确 - 虚拟滚动
function VirtualizedList({ items }: { items: any[] }) {
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style}>
<UserCard user={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
}
🔍 检查清单
在进行性能优化工作或代码审查时,请对照以下检查项:
React 与渲染优化 [P0]
- [P0] 组件渲染优化:是否合理使用
React.memo来包裹不常变化的组件?对于复杂计算或对象/数组依赖,是否使用useMemo进行缓存?对于传递给子组件的回调函数,是否使用useCallback来稳定其引用? - [P0] 代码分割 (路由/组件级):是否对大型页面或非首屏组件使用了
React.lazy和Suspense进行代码分割,以减少初始加载体积? - [P0] 虚拟化列表:对于需要渲染大量数据项的长列表,是否使用了虚拟化技术(如
react-window或react-virtualized)来优化渲染性能和内存占用?
资源与加载优化 [P0]
- [P0] 图片优化:项目中使用的图片是否经过压缩?是否为主要图片格式提供了现代格式(如 WebP)并通过
<picture>元素或内容协商提供? - [P0] 资源懒加载:对于非首屏或非关键的图片、iframe、视频等资源,是否实现了懒加载(Intersection Observer API 或
loading="lazy"属性)? - [P0] Bundle 体积优化:
- 是否实践了按需导入(如
import { specificFunction } from 'library'而非import _ from 'library')? - 项目
package.json及构建配置是否支持并正确利用了 Tree Shaking (如设置sideEffects: false)? - 是否避免了不必要的第三方库依赖或寻找轻量级替代品?
- 是否实践了按需导入(如
用户体验与交互优化 [P0]
- [P0] 防抖 (Debounce):对于频繁触发的事件(如搜索框输入、窗口 resize),是否对相应的处理函数使用了防抖(Debounce)以避免不必要的计算和 API 请求?
- [P0] 节流 (Throttle):对于连续触发的事件(如滚动、鼠标移动),如果需要在固定时间间隔内响应,是否对处理函数使用了节流(Throttle)?
监控与分析 [P1]
- [P1] Web Vitals 核心指标监控:项目中是否集成了 Web Vitals (LCP, FID, CLS) 的监控,并定期分析数据以识别性能瓶颈?
- [P1] Bundle 分析工具:是否定期使用 Bundle 分析工具(如
vite-plugin-inspect,webpack-bundle-analyzer,source-map-explorer)检查打包产物,识别并优化体积过大的模块?
进阶与架构优化 [P2]
- [P2] CDN 使用:项目的静态资源(如 JS, CSS, 图片, 字体)是否通过 CDN (Content Delivery Network) 提供以加速全球访问速度?
- [P2] 服务端渲染 (SSR) / 静态站点生成 (SSG):对于内容型网站或需要极致首屏性能的应用,是否评估或采用了 SSR/SSG 方案?
- [P2] Web Workers:对于 CPU 密集型或长时间运行的计算任务,是否考虑使用 Web Workers 将其移出主线程以避免阻塞 UI?
- [P2] 数据缓存策略:对于不常变化但频繁请求的数据,是否在客户端或服务端(如使用 Redis, Memcached)实施了有效的缓存策略?