关于 react-patterns
面向 React 开发者的模式参考,介绍 React 19、Server Components、Server Actions、组件组织和性能实践。
主要功能
- 01React 19 use() 与状态 Hooks
- 02Server Components 与 Server Actions
- 03Suspense 和错误边界
- 04自定义 Hooks 与复合组件
- 05性能与状态组织建议
适用场景
- 01实现异步数据读取界面
- 02构建服务端表单操作
- 03拆分数据加载边界
- 04复用组件逻辑并整理状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ rohitg00/ awesome-claude-code-toolkit --skill react-patterns安装后在 Claude Code 中输入/react-patterns确认已加载。
详细介绍
React 模式
use() Hook(React 19)
use() 可在渲染时直接读取 Promise 和 Context 中的值。与其他 Hook 不同,它可以在条件语句和循环中调用。
import { use } from 'react';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
function ThemeButton() {
const theme = use(ThemeContext);
return <button style={{ background: theme.primary }}>Click</button>;
}
使用 Promise 调用 use() 的组件应包裹在 <Suspense> 边界中。
服务端组件
// app/users/page.tsx - Server Component (default, no directive needed)
import { UserList } from './UserList';
export default async function UsersPage() {
const users = await fetch('https://api.example.com/users', {
next: { revalidate: 60 },
}).then(r => r.json());
return <UserList users={users} />;
}
// app/users/UserList.tsx - Still a Server Component
export function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map(u => (
<li key={u.id}>
{u.name}
<DeleteButton userId={u.id} />
</li>
))}
</ul>
);
}
尽可能将 'use client' 放在更深层的位置。只有需要交互的叶子组件才应是客户端组件。
服务端操作
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const body = formData.get('body') as string;
await db.insert(posts).values({ title, body });
revalidatePath('/posts');
redirect('/posts');
}
// app/posts/new/page.tsx
import { createPost } from '../actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="body" required />
<button type="submit">Create</button>
</form>
);
}
useActionState(React 19)
'use client';
import { useActionState } from 'react';
import { createUser } from './actions';
function SignupForm() {
const [state, formAction, isPending] = useActionState(createUser, {
errors: {},
message: '',
});
return (
<form action={formAction}>
<input name="email" />
{state.errors.email && <p>{state.errors.email}</p>}
<button disabled={isPending}>
{isPending ? 'Creating...' : 'Sign Up'}
</button>
{state.message && <p>{state.message}</p>}
</form>
);
}
useOptimistic(React 19)
'use client';
import { useOptimistic } from 'react';
import { likePost } from './actions';
function LikeButton({ count, postId }: { count: number; postId: string }) {
const [optimisticCount, addOptimistic] = useOptimistic(count);
async function handleLike() {
addOptimistic(prev => prev + 1);
await likePost(postId);
}
return (
<form action={handleLike}>
<button type="submit">{optimisticCount} Likes</button>
</form>
);
}
Suspense 边界
import { Suspense } from 'react';
function Dashboard() {
return (
<div>
<Suspense fallback={<StatsSkeleton />}>
<StatsPanel />
</Suspense>
<div className="grid grid-cols-2">
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<ListSkeleton />}>
<RecentActivity />
</Suspense>
</div>
</div>
);
}
在彼此独立的数据获取单元周围放置 Suspense 边界。避免用单个边界包裹整个页面(这会让流式传输失去意义)。
错误边界
'use client';
import { Component, type ReactNode } from 'react';
class ErrorBoundary extends Component<
{ fallback: ReactNode; children: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
reportError(error, info.componentStack);
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
也可以使用 Next.js 的 error.tsx 约定处理路由级错误。
自定义 Hook
function useDebounce<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;
}
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
自定义 Hook 的规则:
- 以
use为前缀 - 在 2 个或更多组件之间共享逻辑时,将其提取出来
- 让 Hook 专注于单一职责
- 返回元组
[value, setter]或对象{ data, error, loading }
复合组件
function Tabs({ children }: { children: ReactNode }) {
const [active, setActive] = useState(0);
return (
<TabsContext value={{ active, setActive }}>
<div role="tablist">{children}</div>
</TabsContext>
);
}
Tabs.Tab = function Tab({ index, children }: { index: number; children: ReactNode }) {
const { active, setActive } = use(TabsContext);
return (
<button
role="tab"
aria-selected={active === index}
onClick={() => setActive(index)}
>
{children}
</button>
);
};
Tabs.Panel = function Panel({ index, children }: { index: number; children: ReactNode }) {
const { active } = use(TabsContext);
if (active !== index) return null;
return <div role="tabpanel">{children}</div>;
};
// Usage
<Tabs>
<Tabs.Tab index={0}>Profile</Tabs.Tab>
<Tabs.Tab index={1}>Settings</Tabs.Tab>
<Tabs.Panel index={0}><ProfileForm /></Tabs.Panel>
<Tabs.Panel index={1}><SettingsForm /></Tabs.Panel>
</Tabs>
性能规则
- 避免在 JSX 属性中创建对象或数组(会导致重新渲染)
- 仅在性能分析确认存在不必要的重新渲染后,才使用
React.memo - 对于开销较大的计算,或传递给已记忆化子组件的稳定引用,优先使用
useMemo/useCallback - 使用
key有意重置组件状态 - 就近放置状态:让状态尽可能靠近其使用位置
- 避免超过 2 层的属性逐层传递;改用 Context 或组合
评论
支持 Markdown