Loading...
Loading...
Compare original and translation side by side
| Need | Component Type | Reason |
|---|---|---|
| Direct data fetching (DB, API) | Server (default) | No client JS, faster |
| Event handlers (onClick, onChange) | Client ( | Needs browser interactivity |
| useState / useReducer | Client | State requires client runtime |
| useEffect / useLayoutEffect | Client | Side effects require client |
| Browser APIs (window, localStorage) | Client | Server has no browser |
| Third-party libs using client features | Client | Library requires client |
| No interactivity needed | Server (default) | Smaller bundle, faster |
| 需求 | 组件类型 | 原因 |
|---|---|---|
| 直接获取数据(DB、API) | Server(默认) | 无客户端JS,速度更快 |
| 事件处理(onClick、onChange) | Client( | 需要浏览器交互能力 |
| useState / useReducer | Client | 状态需要客户端运行时支持 |
| useEffect / useLayoutEffect | Client | 副作用需要客户端环境 |
| 浏览器API(window、localStorage) | Client | 服务端无浏览器环境 |
| 使用客户端特性的第三方库 | Client | 库依赖客户端环境 |
| 无需交互能力 | Server(默认) | 打包体积更小,速度更快 |
| Priority | Query | Use For |
|---|---|---|
| 1st | | Any element with ARIA role |
| 2nd | | Form fields |
| 3rd | | Fields without labels |
| 4th | | Non-interactive elements |
| Last | | When nothing else works |
| 优先级 | 查询方法 | 适用场景 |
|---|---|---|
| 1st | | 所有带ARIA角色的元素 |
| 2nd | | 表单字段 |
| 3rd | | 无标签的输入字段 |
| 4th | | 非交互元素 |
| 最后 | | 其他查询都不适用的场景 |
// Functional updates for state based on previous state
setCount(prev => prev + 1);
// Lazy initialization for expensive initial values
const [data, setData] = useState(() => computeExpensiveInitialValue());
// Group related state
const [form, setForm] = useState({ name: '', email: '', role: 'user' });// 基于前序状态更新state时使用函数式更新
setCount(prev => prev + 1);
// 初始值计算成本高时使用惰性初始化
const [data, setData] = useState(() => computeExpensiveInitialValue());
// 关联状态分组管理
const [form, setForm] = useState({ name: '', email: '', role: 'user' });react-hooks/exhaustive-depsreact-hooks/exhaustive-depsuseEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (e) {
if (e.name !== 'AbortError') setError(e);
}
}
fetchData();
return () => controller.abort();
}, [url]);useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (e) {
if (e.name !== 'AbortError') setError(e);
}
}
fetchData();
return () => controller.abort();
}, [url]);| Instead of useEffect for... | Use This |
|---|---|
| Data fetching | React Query, SWR, or Server Components |
| Transforming data | Compute during render |
| User events | Event handlers |
| Syncing external stores | |
| 不要用useEffect做这些事 | 替代方案 |
|---|---|
| 数据获取 | React Query、SWR 或 Server Components |
| 数据转换 | 渲染期间计算 |
| 用户事件处理 | 事件处理函数 |
| 同步外部存储 | |
usefunction useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}usefunction useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div role="tablist">{children}</div>
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ value, children }: TabProps) {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)}>
{children}
</button>
);
};
Tabs.Panel = function Panel({ value, children }: PanelProps) {
const { activeTab } = useTabsContext();
if (activeTab !== value) return null;
return <div role="tabpanel">{children}</div>;
};function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div role="tablist">{children}</div>
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ value, children }: TabProps) {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)}>
{children}
</button>
);
};
Tabs.Panel = function Panel({ value, children }: PanelProps) {
const { activeTab } = useTabsContext();
if (activeTab !== value) return null;
return <div role="tabpanel">{children}</div>;
};| Pattern | Use When | Example |
|---|---|---|
| Compound Components | Related components sharing implicit state | Tabs, Accordion, Menu |
| Slots (Children) | Complex content layout | Card with Header/Body/Footer |
| Render Props | Child needs parent data for flexible rendering | DataFetcher with custom render |
| Higher-Order Component | Cross-cutting concerns (legacy) | withAuth, withTheme |
| Custom Hook | Reusable stateful logic without UI | useDebounce, useLocalStorage |
| 模式 | 适用场景 | 示例 |
|---|---|---|
| 复合组件 | 共享隐式状态的关联组件 | 标签页、折叠面板、菜单 |
| 插槽(Children) | 复杂内容布局 | 带头部/内容/底部的卡片 |
| 渲染属性 | 子组件需要父组件数据实现灵活渲染 | 支持自定义渲染的DataFetcher |
| 高阶组件 | 横切关注点(老旧方案) | withAuth、withTheme |
| 自定义Hook | 无UI的可复用有状态逻辑 | useDebounce、useLocalStorage |
// Prefer composition over props for complex content
// Bad
<Card title="Hello" subtitle="World" icon={<Star />} actions={<Button>Edit</Button>} />
// Good
<Card>
<Card.Header>
<Card.Icon><Star /></Card.Icon>
<Card.Title>Hello</Card.Title>
</Card.Header>
<Card.Actions>
<Button>Edit</Button>
</Card.Actions>
</Card>// 复杂内容优先使用组合而非props传递
// 不好的写法
<Card title="Hello" subtitle="World" icon={<Star />} actions={<Button>Edit</Button>} />
// 推荐写法
<Card>
<Card.Header>
<Card.Icon><Star /></Card.Icon>
<Card.Title>Hello</Card.Title>
</Card.Header>
<Card.Actions>
<Button>Edit</Button>
</Card.Actions>
</Card>| Level | Purpose | Example |
|---|---|---|
| Route level | Catch page-level crashes | |
| Feature level | Isolate feature failures | Wrap each major section |
| Data level | Wrap async data components | Around Suspense boundaries |
| Never leaf level | Too granular, adds noise | Do not wrap individual buttons |
| 层级 | 用途 | 示例 |
|---|---|---|
| 路由层级 | 捕获页面级崩溃 | Next.js中的 |
| 功能模块层级 | 隔离功能故障 | 包裹每个主要功能区块 |
| 数据层级 | 包裹异步数据组件 | Suspense边界外层 |
| 永远不要用在叶子节点层级 | 粒度过细,增加冗余 | 不要包裹单个按钮 |
// Nested Suspense for granular loading
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>// 嵌套Suspense实现细粒度加载
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>| Technique | Use When | Example |
|---|---|---|
| Split contexts by frequency | Some values update often, some rarely | ThemeContext (rare) vs UIStateContext (frequent) |
| Memoize context value | Provider re-renders with same data | |
| Use selectors (Zustand/Jotai) | Need fine-grained subscriptions | |
| Lift state up | Only parent needs to re-render | Pass data as props to memoized children |
| 技术方案 | 适用场景 | 示例 |
|---|---|---|
| 按更新频率拆分Context | 部分值更新频繁,部分很少更新 | ThemeContext(低频) vs UIStateContext(高频) |
| 记忆化Context值 | Provider使用相同数据重渲染时 | |
| 使用选择器(Zustand/Jotai) | 需要细粒度订阅时 | |
| 状态上移 | 仅父组件需要重渲染时 | 将数据作为props传递给记忆化的子组件 |
| Technique | Use When | Do NOT Use When |
|---|---|---|
| Renders often with same props AND re-render is expensive | Props change every render |
| Expensive computation OR referential equality for deps | Simple calculations |
| Stable function ref for memoized children | Function not passed as prop |
| None (default) | Always start here | Premature optimization |
| 技术方案 | 适用场景 | 禁止使用场景 |
|---|---|---|
| 频繁使用相同props渲染且重渲染成本高 | 每次渲染props都会变化 |
| 计算成本高或者依赖需要引用相等时 | 简单计算场景 |
| 需要给记忆化子组件传递稳定的函数引用时 | 函数不会作为props传递 |
| 不使用(默认) | 所有场景优先选择 | 过早优化 |
import { useVirtualizer } from '@tanstack/react-virtual';import { useVirtualizer } from '@tanstack/react-virtual';// Server Component — fetches data directly
async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } });
return (
<div>
<h1>{user.name}</h1>
<UserActions userId={userId} /> {/* Client Component child */}
</div>
);
}
// Client Component — handles interactivity
'use client';
function UserActions({ userId }: { userId: string }) {
const [isFollowing, setIsFollowing] = useState(false);
return <Button onClick={() => toggleFollow(userId)}>Follow</Button>;
}// Server Component —— 直接获取数据
async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } });
return (
<div>
<h1>{user.name}</h1>
<UserActions userId={userId} /> {/* Client Component子组件 */}
</div>
);
}
// Client Component —— 处理交互
'use client';
function UserActions({ userId }: { userId: string }) {
const [isFollowing, setIsFollowing] = useState(false);
return <Button onClick={() => toggleFollow(userId)}>Follow</Button>;
}| Anti-Pattern | Why It Is Wrong | Correct Approach |
|---|---|---|
| Race conditions, no cache, no dedup | React Query or Server Components |
| Prop drilling > 2 levels | Tight coupling, maintenance pain | Composition, context, or Zustand |
| Storing derived state | State that can be computed is unnecessary state | Compute during render |
| Unnecessary effect, stale closures | Derive during render or use key prop |
| Monolithic components (> 200 lines) | Hard to read, test, maintain | Extract sub-components |
| Index as key for dynamic lists | Incorrect reconciliation, stale state | Stable unique ID |
| Direct DOM manipulation | Bypasses React reconciliation | Use refs sparingly, prefer state |
| Testing state values directly | Implementation detail, breaks on refactor | Test user-visible outcomes |
| Memoizing everything | Adds complexity, often slower | Profile first, optimize second |
| 反模式 | 错误原因 | 正确方案 |
|---|---|---|
用 | 存在竞态条件、无缓存、无去重 | React Query或Server Components |
| 超过2层的Props透传 | 耦合度高、维护困难 | 组合、Context或Zustand |
| 存储派生状态 | 可计算得到的状态属于冗余状态 | 渲染期间计算 |
用 | 不必要的effect、闭包过期问题 | 渲染期间派生或使用key属性 |
| 单体组件(超过200行) | 难以阅读、测试、维护 | 拆分提取子组件 |
| 动态列表用索引作为key | 协调错误、状态过期 | 使用稳定的唯一ID |
| 直接操作DOM | 绕过React协调机制 | 尽量少用ref,优先使用状态 |
| 直接测试状态值 | 属于实现细节,重构时容易失效 | 测试用户可见的输出结果 |
| 所有内容都加记忆化 | 增加复杂度,通常反而更慢 | 先做性能分析,再优化 |
mcp__context7__resolve-library-idmcp__context7__query-docsreactnext.jsmcp__context7__resolve-library-idmcp__context7__query-docsreactnext.js| Skill | Relationship |
|---|---|
| Frontend skill uses React patterns from this skill |
| React testing follows the strategy pyramid |
| Component code follows clean code principles |
| React rendering optimization follows measurement methodology |
| E2E tests validate React component behavior |
| Review checks for React anti-patterns |
| UI acceptance criteria drive component tests |
| 技能 | 关联关系 |
|---|---|
| 前端技能使用本技能提供的React模式 |
| React测试遵循测试金字塔策略 |
| 组件代码遵循整洁代码原则 |
| React渲染优化遵循性能测量方法论 |
| E2E测试验证React组件行为 |
| 代码评审检查React反模式 |
| UI验收标准驱动组件测试 |