关于 react-nextjs-patterns
react-nextjs-patterns 是一项面向 React 和 Next.js 前端开发的模式指南,关注性能与可维护性。内容涵盖组件选择、数据获取、状态管理、性能优化和无障碍实践。
主要功能
- 01服务端与客户端组件选择指导
- 02Suspense 与并行数据获取
- 03组件拆分与记忆化
- 04Context、Reducer 与自定义 Hook
- 05无障碍和加载状态检查清单
适用场景
- 01为页面选择服务端或客户端组件
- 02组织 Next.js 并行数据获取
- 03重构组件与状态处理
- 04检查前端性能和无障碍实践
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ duyet/ codex-claude-plugins --skill react-nextjs-patterns安装后在 Claude Code 中输入/react-nextjs-patterns确认已加载。
详细介绍
此技能提供 React 和 Next.js 专属模式,用于构建高性能、易维护的前端应用。
何时调用此技能
在以下场景自动启用:
- 实现 React 组件
- 编写 Next.js 页面和 API 路由
- 使用状态管理模式
- 优化性能
- 决定使用服务端组件还是客户端组件
Next.js App Router 模式
服务端组件与客户端组件
// Server Component (default) - data fetching, no interactivity
// app/users/page.tsx
export default async function UsersPage() {
const users = await getUsers(); // Runs on server
return (
<div>
<h1>Users</h1>
<UserList users={users} />
</div>
);
}
// Client Component - interactivity required
// components/user-search.tsx
'use client';
import { useState } from 'react';
export function UserSearch({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState('');
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onSearch(query)}
/>
);
}
使用 Suspense 实现流式传输
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
{/* Fast data loads first */}
<Suspense fallback={<StatsSkeleton />}>
<StatsSection />
</Suspense>
{/* Slow data streams in */}
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
</div>
);
}
// Async component that streams
async function StatsSection() {
const stats = await getStats(); // Can be slow
return <Stats data={stats} />;
}
数据获取模式
// Parallel data fetching
async function DashboardPage() {
// Fetch in parallel, not sequentially
const [users, orders, stats] = await Promise.all([
getUsers(),
getOrders(),
getStats(),
]);
return <Dashboard users={users} orders={orders} stats={stats} />;
}
// With error boundary
import { notFound } from 'next/navigation';
async function UserPage({ params }: { params: { id: string } }) {
const user = await getUser(params.id);
if (!user) {
notFound(); // Renders not-found.tsx
}
return <UserProfile user={user} />;
}
React 性能模式
组件拆分
// BAD: Large component with all state
function BadUserList() {
const [filter, setFilter] = useState('');
const [users, setUsers] = useState<User[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
// All users re-render on any state change
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
{users.map(user => (
<div
key={user.id}
onClick={() => setSelectedId(user.id)}
className={selectedId === user.id ? 'selected' : ''}
>
{user.name}
</div>
))}
</div>
);
}
// GOOD: Push state down to where it's needed
function GoodUserList() {
const [users] = useState<User[]>([]);
return <FilterableUserList users={users} />;
}
function FilterableUserList({ users }: { users: User[] }) {
const [filter, setFilter] = useState('');
const filtered = useMemo(
() => users.filter(u => u.name.includes(filter)),
[users, filter]
);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<SelectableList users={filtered} />
</div>
);
}
function SelectableList({ users }: { users: User[] }) {
const [selectedId, setSelectedId] = useState<string | null>(null);
return users.map(user => (
<UserItem
key={user.id}
user={user}
selected={selectedId === user.id}
onSelect={() => setSelectedId(user.id)}
/>
));
}
记忆化策略
// Only memo when there's a measurable benefit
const UserItem = memo(function UserItem({
user,
selected,
onSelect
}: {
user: User;
selected: boolean;
onSelect: () => void;
}) {
return (
<div
onClick={onSelect}
className={selected ? 'selected' : ''}
>
{user.name}
</div>
);
});
// useMemo for expensive computations
function ExpensiveList({ items }: { items: Item[] }) {
const processed = useMemo(() => {
return items
.filter(complexFilter)
.sort(complexSort)
.map(complexTransform);
}, [items]);
return <List items={processed} />;
}
// useCallback for stable references passed to children
function Parent() {
const [items, setItems] = useState<Item[]>([]);
const handleDelete = useCallback((id: string) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
return <ItemList items={items} onDelete={handleDelete} />;
}
状态管理模式
使用 Reducer 的 Context
// types
interface State {
user: User | null;
isLoading: boolean;
error: Error | null;
}
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; user: User }
| { type: 'FETCH_ERROR'; error: Error }
| { type: 'LOGOUT' };
// reducer
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'FETCH_START':
return { ...state, isLoading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, isLoading: false, user: action.user };
case 'FETCH_ERROR':
return { ...state, isLoading: false, error: action.error };
case 'LOGOUT':
return { ...state, user: null };
}
}
// context
const AuthContext = createContext<{
state: State;
dispatch: Dispatch<Action>;
} | null>(null);
// provider
function AuthProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
user: null,
isLoading: true,
error: null,
});
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
);
}
// hook
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
自定义 Hooks
// Data fetching hook
function useQuery<T>(
key: string,
fetcher: () => Promise<T>
): { data: T | null; isLoading: boolean; error: Error | null; refetch: () => void } {
const [data, setData] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const result = await fetcher();
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setIsLoading(false);
}
}, [fetcher]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, isLoading, error, refetch: fetchData };
}
// Debounced value hook
function useDebouncedValue<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// Local storage hook
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = useCallback((value: T | ((prev: T) => T)) => {
setStoredValue(prev => {
const newValue = value instanceof Function ? value(prev) : value;
localStorage.setItem(key, JSON.stringify(newValue));
return newValue;
});
}, [key]);
return [storedValue, setValue];
}
组件模式
复合组件
// Flexible API with compound components
const Tabs = ({ children, defaultValue }: { children: ReactNode; defaultValue: string }) => {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
};
Tabs.List = function TabsList({ children }: { children: ReactNode }) {
return <div className="tabs-list">{children}</div>;
};
Tabs.Tab = function Tab({ value, children }: { value: string; children: ReactNode }) {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button
className={activeTab === value ? 'active' : ''}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
};
Tabs.Panel = function TabsPanel({ value, children }: { value: string; children: ReactNode }) {
const { activeTab } = useTabsContext();
if (activeTab !== value) return null;
return <div className="tabs-panel">{children}</div>;
};
// Usage
<Tabs defaultValue="tab1">
<Tabs.List>
<Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab1">Content 1</Tabs.Panel>
<Tabs.Panel value="tab2">Content 2</Tabs.Panel>
</Tabs>
Render Props 与函数作为 Children
// Data provider with render prop
function DataProvider<T>({
fetcher,
children,
}: {
fetcher: () => Promise<T>;
children: (data: T | null, isLoading: boolean) => ReactNode;
}) {
const { data, isLoading } = useQuery('data', fetcher);
return <>{children(data, isLoading)}</>;
}
// Usage
<DataProvider fetcher={getUsers}>
{(users, isLoading) => (
isLoading ? <Spinner /> : <UserList users={users} />
)}
</DataProvider>
最佳实践检查清单
- 默认使用服务端组件,仅在必要时使用客户端组件
- 将状态下放到需要它的最底层组件
- 将大型组件拆分为更小、更专注的组件
- 使用 Suspense 边界处理异步操作
- 仅在性能分析显示有益时进行记忆化
- 为可复用的有状态逻辑创建自定义 Hooks
- 使用判别联合类型表示组件状态
- 正确实现错误边界
- 确保可访问性(ARIA、键盘导航)
- 使用恰当的加载和错误状态
评论
支持 Markdown