Skip to main content

react-best-practices

React宝典——React 与 Next.js 全栈最佳实践指南。 当用户编写、审查或重构 React/Next.js 代码时自动激活。 触发场景:React 最佳实践、React 怎么写、组件设计、hooks 用法、hooks 怎么用、 状态管理、性能优化、React 性能、Server Components、RSC、Next.js 集成、 数据获取、bundle 优化、重渲染优化、React 19 新特性、use hook、 React best practices、component design patterns、React performance、 how to write React、Next.js best practices、React Server Components。 覆盖:组件设计模式、Hooks 最佳实践、状态管理策略、性能优化、 Server Components、Next.js 15+ 集成、React 19+ 新 API。

الانتقال إلى التثبيت

معلومات المصدر

المستودع
sahit-sai/saviaa
آخر نشاط في المصدر
١٨ يوليو ٢٠٢٦ في ١١:٠٣
لغة SKILL.md المكتشفة
الصينية
النجوم
٠
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
react-best-practices
description
|
## 适用时机 在以下场景中使用本指南: - 编写新的 React 组件或页面 - 实现数据获取逻辑 - 审查代码性能问题 - 重构已有代码 - 优化 bundle 大小和加载时间 - 迁移到 React 19+ / Next.js 15+ / Server Components --- ## 一、消除请求瀑布(CRITICAL) 请求瀑布是性能的头号杀手。多个异步操作串行执行时,总延迟 = 所有操作之和。 ### 1.1 延迟 await 到真正需要的位置 ```typescript // ❌ 阻塞整个函数 async function Page() { const user = await getUser(); const posts = await getPosts(); // 必须等 getUser 完成 return <Feed user={user} posts={posts} />; } // ✅ 并行发起,延迟 await async function Page() { const userPromise = getUser(); const postsPromise = getPosts(); const [user, posts] = await Promise.all([userPromise, postsPromise]); return <Feed user={user} posts={posts} />; } ``` ### 1.2 用 Promise.all() 并行化独立操作 当多个异步操作互不依赖时,始终使用 `Promise.all()` 并行执行。 ```typescript // ❌ 串行:总时间 = T1 + T2 + T3 const user = await fetchUser(id); const orders = await fetchOrders(id); const recommendations = await fetchRecommendations(id); // ✅ 并行:总时间 = max(T1, T2, T3) const [user, orders, recommendations] = await Promise.all([ fetchUser(id), fetchOrders(id), fetchRecommendations(id), ]); ``` ### 1.3 策略性放置 Suspense 边界 用 Suspense 拆分页面,让快的部分先展示,慢的部分异步加载。 ```tsx // ✅ 外壳立即可见,数据部分逐步流式加载 export default function DashboardPage() { return ( <DashboardShell> <Suspense fallback={<StatsSkeleton />}> <StatsPanel /> </Suspense> <Suspense fallback={<FeedSkeleton />}> <ActivityFeed /> </Suspense> </DashboardShell> ); } ``` ### 1.4 API Route 中避免瀑布链 在 API Route / Server Action 中同样适用——先发起所有请求,再 await。 ### 1.5 有依赖关系时最大化并行度 当操作之间存在部分依赖时,用依赖图分析哪些可以并行。 ```typescript // ✅ user 和 config 并行;posts 依赖 user,但 config 不阻塞它 const [user, config] = await Promise.all([getUser(), getConfig()]); const posts = await getPosts(user.id); ``` --- ## 二、Bundle 体积优化(CRITICAL) ### 2.1 避免 Barrel File 导入 Barrel file(`index.ts` 聚合导出)会导致 tree-shaking 失效,一个图标可能拉入整个图标库。 ```typescript // ❌ 拉入全部图标(200KB+) import { Check } from "lucide-react"; // ✅ 直接从源文件导入 import Check from "lucide-react/dist/esm/icons/check"; ``` 适用于所有 icon 库、UI 组件库、工具库。 ### 2.2 动态导入重型组件 对大型组件(编辑器、图表、地图等)使用 `next/dynamic` 或 `React.lazy`。 ```typescript // ✅ Monaco Editor (~300KB) 仅在需要时加载 import dynamic from "next/dynamic"; const CodeEditor = dynamic(() => import("@/components/CodeEditor"), { loading: () => <EditorSkeleton />, ssr: false, }); ``` ### 2.3 延迟加载非关键第三方库 分析脚本、错误追踪等不应阻塞首屏。 ```typescript // ✅ hydration 完成后再加载分析脚本 useEffect(() => { import("@analytics/core").then((mod) => mod.init()); }, []); ``` ### 2.4 条件加载模块 仅在功能激活时才加载对应的模块/数据。 ### 2.5 基于用户意图预加载 在 hover/focus 时预加载即将使用的组件。 ```tsx // ✅ hover 时预加载,点击时立即可用 <button onMouseEnter={() => import("@/components/SettingsPanel")} onClick={() => setShowSettings(true)} > 设置 </button> ``` --- ## 三、服务端性能(HIGH) ### 3.1 Server Action 必须验证身份 Server Action 是公开端点,必须像 API Route 一样验证认证和授权。 ```typescript "use server"; export async function deletePost(postId: string) { const session = await auth(); // ✅ 始终验证 if (!session) throw new Error("Unauthorized"); if (!await canDelete(session.user.id, postId)) throw new Error("Forbidden"); await db.post.delete({ where: { id: postId } }); } ``` ### 3.2 最小化 RSC 边界的序列化 Server Component 传给 Client Component 的每个 prop 都会被序列化进 HTML。只传客户端需要的字段。 ```typescript // ❌ 传了整个 user 对象(包含敏感字段) <UserCard user={user} /> // ✅ 只传需要的字段 <UserCard name={user.name} avatar={user.avatar} /> ``` ### 3.3 用 React.cache() 做请求内去重 同一次请求中多个组件调用同一个数据函数时,用 `React.cache()` 避免重复执行。 ```typescript import { cache } from "react"; export const getUser = cache(async (id: string) => { return db.user.findUnique({ where: { id } }); }); // 同一请求中多次调用 getUser(id) 只执行一次 ``` ### 3.4 跨请求使用 LRU 缓存 `React.cache()` 仅在单次请求内生效。对跨请求共享的数据,使用 LRU 缓存。 ```typescript import { LRUCache } from "lru-cache"; const cache = new LRUCache<string, any>({ max: 500, ttl: 1000 * 60 * 5 }); export async function getConfig() { const cached = cache.get("config"); if (cached) return cached; const config = await db.config.findFirst(); cache.set("config", config); return config; } ``` ### 3.5 静态 I/O 提升到模块级别 字体文件、Logo、静态配置等不要每次请求都读取。 ```typescript // ✅ 模块加载时读取一次 const logoBuffer = await fs.readFile("./public/logo.png"); export function Logo() { return <img src={`data:image/png;base64,${logoBuffer.toString("base64")}`} />; } ``` ### 3.6 用 after() 做非阻塞操作 日志、埋点等不影响响应的操作用 `after()` 推迟到响应发送之后。 ```typescript import { after } from "next/server"; export async function POST(request: Request) { const result = await processOrder(request); after(async () => { await logAnalytics({ event: "order_created", orderId: result.id }); }); return Response.json(result); } ``` ### 3.7 并行数据获取 + 组件组合 利用 Server Component 的组合特性,让独立的数据获取自动并行。 ### 3.8 避免重复序列化 不要同时传原始数组和衍生状态,让客户端自行计算。 --- ## 四、客户端数据获取(MEDIUM-HIGH) ### 4.1 用 SWR/TanStack Query 自动去重 多个组件实例订阅同一数据时自动共享请求。 ```typescript // ✅ 多个组件调用同一 key,只发一次请求 function useUser(id: string) { return useSWR(`/api/users/${id}`, fetcher); } ``` ### 4.2 全局事件监听器去重 用 `useSyncExternalStore` 或 `useSWRSubscription` 共享 WebSocket/事件源。 ### 4.3 使用 Passive Event Listener 滚动、触摸事件加 `{ passive: true }`,消除浏览器滚动延迟。 ```typescript // ✅ 不阻塞滚动 element.addEventListener("touchstart", handler, { passive: true }); ``` ### 4.4 localStorage 加版本号并最小化存储 ```typescript // ✅ 带版本前缀 + 只存必要字段 + try-catch 包裹 const STORAGE_KEY = "app_prefs_v2"; function savePrefs(prefs: UserPrefs) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ theme: prefs.theme, lang: prefs.lang, })); } catch (e) { // 存储已满或隐私模式 } } ``` --- ## 五、重渲染优化(MEDIUM) ### 5.1 渲染期间计算派生状态 不要把可以计算的值存进 state——直接在渲染中计算。 ```typescript // ❌ 多余的 state + effect const [items, setItems] = useState(data); const [filteredItems, setFilteredItems] = useState([]); useEffect(() => { setFilteredItems(items.filter((i) => i.active)); }, [items]); // ✅ 渲染期间直接计算 const [items, setItems] = useState(data); const filteredItems = items.filter((i) => i.active); ``` ### 5.2 不要在组件内定义组件 内联组件每次父组件渲染都会导致完全卸载+重新挂载。 ```typescript // ❌ 每次渲染都创建新组件 function Parent() { function Child() { return <div>child</div>; } // 别这样做 return <Child />; } // ✅ 提取到外部,用 props 传递数据 function Child({ data }: { data: string }) { return <div>{data}</div>; } function Parent() { return <Child data="hello" />; } ``` ### 5.3 用 useRef 存储不触发渲染的频繁变化值 鼠标位置、滚动进度、计时器 ID 等不需要触发 UI 更新的值,用 ref 而非 state。 ### 5.4 用 useMemo/React.memo 控制子组件渲染 但注意:**简单表达式不要包 useMemo**。`useMemo` 本身有开销,布尔值/数字/字符串的简单计算不值得 memo。 ```typescript // ❌ 过度 memo const isActive = useMemo(() => status === "active", [status]); // ✅ 直接计算 const isActive = status === "active"; ``` ### 5.5 用函数式 setState 避免闭包陷阱 ```typescript // ❌ 可能使用过期的 count setCount(count + 1); // ✅ 始终基于最新值 setCount((prev) => prev + 1); ``` ### 5.6 useState 惰性初始化 传函数给 `useState`,仅在首次渲染时执行。 ```typescript // ❌ 每次渲染都执行 expensive() const [data, setData] = useState(expensiveComputation()); // ✅ 只在挂载时执行一次 const [data, setData] = useState(() => expensiveComputation()); ``` ### 5.7 提取非原始值默认值为常量 ```typescript // ❌ 每次渲染新建对象,memo 失效 function List({ config = { pageSize: 10 } }) { ... } // ✅ 稳定引用 const DEFAULT_CONFIG = { pageSize: 10 }; function List({ config = DEFAULT_CONFIG }) { ... } ``` ### 5.8 缩窄 Effect 依赖 订阅派生的布尔值而非连续变化的值。 ```typescript // ❌ value 每次变化都触发 effect useEffect(() => { ... }, [value]); // ✅ 只在 isAboveThreshold 变化时触发 const isAboveThreshold = value > 100; useEffect(() => { ... }, [isAboveThreshold]); ``` ### 5.9 把交互逻辑放进事件处理器 用户行为 -> 事件处理器,不是 state + effect 间接表达。 ### 5.10 延迟读取动态状态 如果只在回调中读取 searchParams/localStorage,不要在渲染层订阅它们。 ### 5.11 用 startTransition 标记非紧急更新 ```typescript // ✅ 搜索输入立即响应,结果列表延迟更新 function Search() { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); function handleChange(e: React.ChangeEvent<HTMLInputElement>) { setQuery(e.target.value); startTransition(() => { setResults(searchItems(e.target.value)); }); } return ( <> <input value={query} onChange={handleChange} /> <ResultList results={results} /> </> ); } ``` ### 5.12 提取为 Memo 组件实现提前返回 ### 5.13 Effect 内不该做的事 -> 移到事件处理器 --- ## 六、渲染性能(MEDIUM) ### 6.1 用 CSS content-visibility 优化长列表 ```css /* ✅ 浏览器跳过屏幕外内容的渲染 */ .list-item { content-visibility: auto; contain-intrinsic-size: 0 80px; } ``` ### 6.2 静态 JSX 提升到组件外部 不会变化的 JSX 片段提取为模块级常量,避免每次渲染重新创建。 ```typescript // ✅ 只创建一次 const EMPTY_STATE = <div className="empty">暂无数据</div>; function List({ items }: { items: Item[] }) { if (items.length === 0) return EMPTY_STATE; return <ul>{items.map(...)}</ul>; } ``` ### 6.3 防止 Hydration 闪烁 依赖 localStorage/cookie 的 UI(如暗色模式)用同步脚本在 React hydrate 之前设置。 ```html <!-- ✅ 在 <head> 中同步执行,hydration 前完成 --> <script> const theme = localStorage.getItem("theme") || "light"; document.documentElement.dataset.theme = theme; </script> ``` ### 6.4 使用 Activity 组件(React 19+) 频繁切换显示/隐藏的重型组件,用 `<Activity>` 保留 state 和 DOM。 ```tsx // ✅ 切换时保留状态,不重新挂载 import { Activity } from "react"; <Activity mode={activeTab === "editor" ? "visible" : "hidden"}> <HeavyEditor /> </Activity> ``` ### 6.5 SVG 动画包裹 div 直接在 SVG 元素上做 CSS 动画会跳过 GPU 加速。包一层 div。 ### 6.6 优化 SVG 精度 用 SVGO 将坐标精度降到 1 位小数,减少 SVG 体积 20-40%。
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub