mcpField
R
react-patterns

react-patterns

github.com/rohitg00开发2.8k212 次安装25 周前更新
代码仓库

关于 react-patterns

面向 React 开发者的模式参考,介绍 React 19、Server Components、Server Actions、组件组织和性能实践。

主要功能

  1. 01React 19 use() 与状态 Hooks
  2. 02Server Components 与 Server Actions
  3. 03Suspense 和错误边界
  4. 04自定义 Hooks 与复合组件
  5. 05性能与状态组织建议

适用场景

  1. 01实现异步数据读取界面
  2. 02构建服务端表单操作
  3. 03拆分数据加载边界
  4. 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