react-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Best Practices

React最佳实践

Comprehensive guide for building modern React and Next.js applications. Covers performance optimization, component architecture, shadcn/ui patterns, Motion animations, accessibility, and React 19+ features.
构建现代React和Next.js应用的综合指南,内容涵盖性能优化、组件架构、shadcn/ui模式、Motion动画、可访问性以及React 19+新特性。

When to Apply

适用场景

Reference these guidelines when:
  • Writing new React components or Next.js pages
  • Implementing data fetching (client or server-side)
  • Building UI with shadcn/ui components
  • Adding animations and micro-interactions
  • Reviewing code for quality and performance
  • Refactoring existing React/Next.js code
  • Optimizing bundle size or load times
在以下场景中可参考本指南:
  • 编写新的React组件或Next.js页面
  • 实现客户端或服务端数据请求逻辑
  • 使用shadcn/ui组件搭建UI界面
  • 添加动画和微交互效果
  • 评审代码的质量与性能表现
  • 重构现有React/Next.js代码
  • 优化包体积或页面加载速度

Rule Categories by Priority

按优先级划分的规则分类

PriorityCategoryImpactPrefix
1Component ArchitectureCRITICAL
arch-
2Eliminating WaterfallsCRITICAL
async-
3Bundle Size OptimizationCRITICAL
bundle-
4Server Components & ActionsHIGH
server-
5shadcn/ui PatternsHIGH
shadcn-
6State ManagementMEDIUM-HIGH
state-
7Motion & AnimationsMEDIUM
motion-
8Re-render OptimizationMEDIUM
rerender-
9AccessibilityMEDIUM
a11y-
10TypeScript PatternsMEDIUM
ts-

优先级分类影响程度前缀
1组件架构最高
arch-
2消除请求瀑布流最高
async-
3包体积优化最高
bundle-
4服务端组件与服务端操作
server-
5shadcn/ui设计模式
shadcn-
6状态管理中高
state-
7Motion与动画实现
motion-
8重渲染优化
rerender-
9可访问性
a11y-
10TypeScript设计模式
ts-

1. Component Architecture (CRITICAL)

1. 组件架构(最高优先级)

Quick Reference

快速参考

  • arch-functional-components
    - Use functional components with hooks exclusively
  • arch-composition-over-inheritance
    - Build on existing components, don't extend
  • arch-single-responsibility
    - Each component should do one thing well
  • arch-presentational-container
    - Separate UI from logic when beneficial
  • arch-colocation
    - Keep related files together (component, styles, tests)
  • arch-avoid-prop-drilling
    - Use Context or composition for deep props
  • arch-functional-components
    - 仅使用带Hook的函数式组件
  • arch-composition-over-inheritance
    - 基于现有组件组合构建,不使用继承
  • arch-single-responsibility
    - 每个组件仅专注于完成一项功能
  • arch-presentational-container
    - 合适情况下将UI与业务逻辑分离
  • arch-colocation
    - 相关文件放在同一目录下(组件、样式、测试)
  • arch-avoid-prop-drilling
    - 深层传参使用Context或组件组合,避免prop drilling

Key Principles

核心原则

Functional Components Only
typescript
// Correct: Functional component with hooks
function UserProfile({ userId }: { userId: string }) {
  const { data: user } = useUser(userId)
  return <div>{user?.name}</div>
}

// Incorrect: Class component
class UserProfile extends React.Component { /* ... */ }
Composition Pattern
typescript
// Correct: Compose smaller components
function Card({ children }: { children: React.ReactNode }) {
  return <div className="rounded-lg border p-4">{children}</div>
}

function CardHeader({ children }: { children: React.ReactNode }) {
  return <div className="font-semibold">{children}</div>
}

// Usage
<Card>
  <CardHeader>Title</CardHeader>
  <p>Content</p>
</Card>
Avoid Prop Drilling
typescript
// Incorrect: Passing props through many levels
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />
    </Sidebar>
  </Layout>
</App>

// Correct: Use Context for shared state
const UserContext = createContext<User | null>(null)

function App() {
  const user = useCurrentUser()
  return (
    <UserContext.Provider value={user}>
      <Layout>
        <Sidebar>
          <UserMenu />
        </Sidebar>
      </Layout>
    </UserContext.Provider>
  )
}

仅使用函数式组件
typescript
// 正确写法:带Hook的函数式组件
function UserProfile({ userId }: { userId: string }) {
  const { data: user } = useUser(userId)
  return <div>{user?.name}</div>
}

// 错误写法:类组件
class UserProfile extends React.Component { /* ... */ }
组合模式
typescript
// 正确写法:组合小型组件
function Card({ children }: { children: React.ReactNode }) {
  return <div className="rounded-lg border p-4">{children}</div>
}

function CardHeader({ children }: { children: React.ReactNode }) {
  return <div className="font-semibold">{children}</div>
}

// 使用示例
<Card>
  <CardHeader>标题</CardHeader>
  <p>内容</p>
</Card>
避免Prop Drilling
typescript
// 错误写法:跨多层传递props
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />
    </Sidebar>
  </Layout>
</App>

// 正确写法:共享状态使用Context
const UserContext = createContext<User | null>(null)

function App() {
  const user = useCurrentUser()
  return (
    <UserContext.Provider value={user}>
      <Layout>
        <Sidebar>
          <UserMenu />
        </Sidebar>
      </Layout>
    </UserContext.Provider>
  )
}

2. Eliminating Waterfalls (CRITICAL)

2. 消除请求瀑布流(最高优先级)

Quick Reference

快速参考

  • async-defer-await
    - Move await into branches where actually used
  • async-parallel
    - Use Promise.all() for independent operations
  • async-dependencies
    - Handle partial dependencies correctly
  • async-api-routes
    - Start promises early, await late in API routes
  • async-suspense-boundaries
    - Use Suspense to stream content
  • async-defer-await
    - 将await移到实际需要使用的分支中
  • async-parallel
    - 独立请求使用Promise.all()并行处理
  • async-dependencies
    - 正确处理存在依赖关系的请求
  • async-api-routes
    - API路由中尽早发起请求,延后await
  • async-suspense-boundaries
    - 使用Suspense实现内容流式加载

Key Principles

核心原则

Waterfalls are the #1 performance killer. Each sequential await adds full network latency.
Parallel Data Fetching
typescript
// Incorrect: Sequential waterfalls
async function Page() {
  const user = await fetchUser()
  const posts = await fetchPosts()
  const comments = await fetchComments()
  return <div>{/* render */}</div>
}

// Correct: Parallel fetching
async function Page() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ])
  return <div>{/* render */}</div>
}
Strategic Suspense Boundaries
typescript
// Stream content as it becomes available
function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments />
      </Suspense>
    </div>
  )
}

请求瀑布流是性能的头号杀手,每个串行await都会增加完整的网络延迟。
并行数据请求
typescript
// 错误写法:串行请求产生瀑布流
async function Page() {
  const user = await fetchUser()
  const posts = await fetchPosts()
  const comments = await fetchComments()
  return <div>{/* 渲染内容 */}</div>
}

// 正确写法:并行请求
async function Page() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ])
  return <div>{/* 渲染内容 */}</div>
}
合理设置Suspense边界
typescript
// 内容就绪后流式渲染
function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments />
      </Suspense>
    </div>
  )
}

3. Bundle Size Optimization (CRITICAL)

3. 包体积优化(最高优先级)

Quick Reference

快速参考

  • bundle-barrel-imports
    - Import directly, avoid barrel files
  • bundle-dynamic-imports
    - Use next/dynamic for heavy components
  • bundle-defer-third-party
    - Load analytics/logging after hydration
  • bundle-conditional
    - Load modules only when feature is activated
  • bundle-preload
    - Preload on hover/focus for perceived speed
  • bundle-barrel-imports
    - 直接导入模块,避免使用桶文件
  • bundle-dynamic-imports
    - 重型组件使用next/dynamic动态导入
  • bundle-defer-third-party
    - 水合完成后再加载统计/日志等第三方资源
  • bundle-conditional
    - 仅在功能启用时加载对应模块
  • bundle-preload
    - hover/聚焦时预加载资源,提升感知速度

Key Principles

核心原则

Avoid Barrel File Imports
typescript
// Incorrect: Imports entire library
import { Button } from '@/components'
import { formatDate } from '@/utils'

// Correct: Direct imports enable tree-shaking
import { Button } from '@/components/ui/button'
import { formatDate } from '@/utils/date'
Dynamic Imports
typescript
import dynamic from 'next/dynamic'

// Load only when needed
const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false
})

function Dashboard({ showChart }) {
  return showChart ? <HeavyChart /> : null
}

避免桶文件导入
typescript
// 错误写法:导入整个库
import { Button } from '@/components'
import { formatDate } from '@/utils'

// 正确写法:直接导入,支持tree-shaking
import { Button } from '@/components/ui/button'
import { formatDate } from '@/utils/date'
动态导入
typescript
import dynamic from 'next/dynamic'

// 仅在需要时加载
const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false
})

function Dashboard({ showChart }) {
  return showChart ? <HeavyChart /> : null
}

4. Server Components & Actions (HIGH)

4. 服务端组件与服务端操作(高优先级)

Quick Reference

快速参考

  • server-default-server
    - Components are Server Components by default
  • server-use-client-boundary
    - Add 'use client' only when needed
  • server-actions
    - Use Server Actions for mutations
  • server-cache-react
    - Use React.cache() for per-request deduplication
  • server-serialization
    - Minimize data passed to client components
  • server-default-server
    - 组件默认是服务端组件
  • server-use-client-boundary
    - 仅在需要交互时添加'use client'指令
  • server-actions
    - 数据变更使用Server Actions
  • server-cache-react
    - 同请求去重使用React.cache()
  • server-serialization
    - 最小化传递给客户端组件的数据量

Key Principles

核心原则

Server Components by Default
typescript
// Server Component (default) - can be async
async function ProductPage({ id }: { id: string }) {
  const product = await db.product.findUnique({ where: { id } })
  return <ProductDetails product={product} />
}

// Client Component - only when needed for interactivity
'use client'
function AddToCartButton({ productId }: { productId: string }) {
  const [isPending, startTransition] = useTransition()

  return (
    <Button
      onClick={() => startTransition(() => addToCart(productId))}
      disabled={isPending}
    >
      Add to Cart
    </Button>
  )
}
Server Actions
typescript
// actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.post.create({ data: { title, content } })
  revalidatePath('/posts')
}

// Component usage
function CreatePostForm() {
  return (
    <form action={createPost}>
      <Input name="title" placeholder="Title" />
      <Textarea name="content" placeholder="Content" />
      <Button type="submit">Create Post</Button>
    </form>
  )
}

默认使用服务端组件
typescript
// 默认是服务端组件 - 支持async
async function ProductPage({ id }: { id: string }) {
  const product = await db.product.findUnique({ where: { id } })
  return <ProductDetails product={product} />
}

// 客户端组件 - 仅在需要交互时使用
'use client'
function AddToCartButton({ productId }: { productId: string }) {
  const [isPending, startTransition] = useTransition()

  return (
    <Button
      onClick={() => startTransition(() => addToCart(productId))}
      disabled={isPending}
    >
      加入购物车
    </Button>
  )
}
Server Actions
typescript
// actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.post.create({ data: { title, content } })
  revalidatePath('/posts')
}

// 组件中使用
function CreatePostForm() {
  return (
    <form action={createPost}>
      <Input name="title" placeholder="标题" />
      <Textarea name="content" placeholder="内容" />
      <Button type="submit">发布帖子</Button>
    </form>
  )
}

5. shadcn/ui Patterns (HIGH)

5. shadcn/ui设计模式(高优先级)

Quick Reference

快速参考

  • shadcn-composition
    - Build on existing shadcn/ui primitives
  • shadcn-variants
    - Use class-variance-authority for component variants
  • shadcn-theme-integration
    - Use CSS custom properties for theming
  • shadcn-accessibility
    - Leverage built-in accessibility from Radix
  • shadcn-customization
    - Modify copied components, don't wrap excessively
  • shadcn-composition
    - 基于现有shadcn/ui基础组件构建
  • shadcn-variants
    - 使用class-variance-authority定义组件变体
  • shadcn-theme-integration
    - 使用CSS自定义属性实现主题
  • shadcn-accessibility
    - 复用Radix内置的可访问性能力
  • shadcn-customization
    - 直接修改复制的组件,避免过度封装

Core Principles

核心原则

shadcn/ui is built around:
  • Open Code: Components are copied into your project, fully customizable
  • Composition: Every component uses a common, composable interface
  • Beautiful Defaults: Carefully chosen default styles
  • Accessibility by Default: Built on Radix UI primitives
shadcn/ui的设计理念是:
  • 代码开放:组件复制到项目中,完全可自定义
  • 组合优先:所有组件使用通用、可组合的接口
  • 美观默认值:精心设计的默认样式
  • 默认支持可访问性:基于Radix UI基础组件构建

Component Installation

组件安装

bash
undefined
bash
undefined

Add components as needed

按需添加组件

npx shadcn@latest add button npx shadcn@latest add card npx shadcn@latest add dialog npx shadcn@latest add form
undefined
npx shadcn@latest add button npx shadcn@latest add card npx shadcn@latest add dialog npx shadcn@latest add form
undefined

Building Custom Components

自定义组件构建

Composition Over Creation
typescript
// Correct: Build on existing primitives
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'

interface ProductCardProps {
  product: Product
  onSelect?: () => void
}

function ProductCard({ product, onSelect }: ProductCardProps) {
  return (
    <Card
      className="cursor-pointer hover:shadow-md transition-shadow"
      onClick={onSelect}
    >
      <CardHeader>
        <div className="flex items-center justify-between">
          <CardTitle>{product.name}</CardTitle>
          {product.isNew && <Badge>New</Badge>}
        </div>
      </CardHeader>
      <CardContent>
        <p className="text-muted-foreground">{product.description}</p>
        <p className="text-lg font-bold mt-2">${product.price}</p>
      </CardContent>
    </Card>
  )
}
Using Variants with CVA
typescript
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const statusBadgeVariants = cva(
  'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
  {
    variants: {
      status: {
        pending: 'bg-yellow-100 text-yellow-800',
        active: 'bg-green-100 text-green-800',
        inactive: 'bg-gray-100 text-gray-800',
        error: 'bg-red-100 text-red-800',
      },
    },
    defaultVariants: {
      status: 'pending',
    },
  }
)

interface StatusBadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof statusBadgeVariants> {
  label: string
}

function StatusBadge({ status, label, className, ...props }: StatusBadgeProps) {
  return (
    <span className={cn(statusBadgeVariants({ status }), className)} {...props}>
      {label}
    </span>
  )
}
优先组合而非新建
typescript
// 正确写法:基于现有基础组件构建
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'

interface ProductCardProps {
  product: Product
  onSelect?: () => void
}

function ProductCard({ product, onSelect }: ProductCardProps) {
  return (
    <Card
      className="cursor-pointer hover:shadow-md transition-shadow"
      onClick={onSelect}
    >
      <CardHeader>
        <div className="flex items-center justify-between">
          <CardTitle>{product.name}</CardTitle>
          {product.isNew && <Badge>新品</Badge>}
        </div>
      </CardHeader>
      <CardContent>
        <p className="text-muted-foreground">{product.description}</p>
        <p className="text-lg font-bold mt-2">¥{product.price}</p>
      </CardContent>
    </Card>
  )
}
使用CVA定义变体
typescript
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const statusBadgeVariants = cva(
  'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
  {
    variants: {
      status: {
        pending: 'bg-yellow-100 text-yellow-800',
        active: 'bg-green-100 text-green-800',
        inactive: 'bg-gray-100 text-gray-800',
        error: 'bg-red-100 text-red-800',
      },
    },
    defaultVariants: {
      status: 'pending',
    },
  }
)

interface StatusBadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof statusBadgeVariants> {
  label: string
}

function StatusBadge({ status, label, className, ...props }: StatusBadgeProps) {
  return (
    <span className={cn(statusBadgeVariants({ status }), className)} {...props}>
      {label}
    </span>
  )
}

Common shadcn/ui Components

常用shadcn/ui组件

Forms with React Hook Form + Zod
typescript
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'

const formSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
})

function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: '',
      password: '',
    },
  })

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="you@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" className="w-full">Sign In</Button>
      </form>
    </Form>
  )
}
Dialog/Modal Pattern
typescript
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'

function ConfirmDialog({
  onConfirm,
  title,
  description
}: {
  onConfirm: () => void
  title: string
  description: string
}) {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
          <DialogDescription>{description}</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline">Cancel</Button>
          <Button variant="destructive" onClick={onConfirm}>
            Confirm
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
Data Table with Tanstack Table
typescript
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from '@tanstack/react-table'

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}

function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  })

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <TableHead key={header.id}>
                  {flexRender(
                    header.column.columnDef.header,
                    header.getContext()
                  )}
                </TableHead>
              ))}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows.map((row) => (
            <TableRow key={row.id}>
              {row.getVisibleCells().map((cell) => (
                <TableCell key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

结合React Hook Form + Zod实现表单
typescript
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'

const formSchema = z.object({
  email: z.string().email('邮箱地址无效'),
  password: z.string().min(8, '密码长度不能少于8位'),
})

function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: '',
      password: '',
    },
  })

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>邮箱</FormLabel>
              <FormControl>
                <Input placeholder="you@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>密码</FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" className="w-full">登录</Button>
      </form>
    </Form>
  )
}
对话框/弹窗模式
typescript
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'

function ConfirmDialog({
  onConfirm,
  title,
  description
}: {
  onConfirm: () => void
  title: string
  description: string
}) {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">删除</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
          <DialogDescription>{description}</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline">取消</Button>
          <Button variant="destructive" onClick={onConfirm}>
            确认
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
结合Tanstack Table实现数据表格
typescript
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from '@tanstack/react-table'

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}

function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  })

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <TableHead key={header.id}>
                  {flexRender(
                    header.column.columnDef.header,
                    header.getContext()
                  )}
                </TableHead>
              ))}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows.map((row) => (
            <TableRow key={row.id}>
              {row.getVisibleCells().map((cell) => (
                <TableCell key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

6. State Management (MEDIUM-HIGH)

6. 状态管理(中高优先级)

Quick Reference

快速参考

  • state-local-first
    - Use useState/useReducer for local state
  • state-context-static
    - Use Context for infrequently changing data
  • state-derived-compute
    - Compute derived values, don't store them
  • state-url-state
    - Use URL for shareable/bookmarkable state
  • state-server-state
    - Use SWR/TanStack Query for server state
  • state-local-first
    - 本地状态优先使用useState/useReducer
  • state-context-static
    - 不常变更的数据使用Context存储
  • state-derived-compute
    - 衍生值直接计算,不单独存储
  • state-url-state
    - 可分享/可书签的状态存储在URL中
  • state-server-state
    - 服务端状态使用SWR/TanStack Query管理

Key Principles

核心原则

Avoid Derived State
typescript
// Incorrect: Storing derived state
function ProductList({ products }) {
  const [items, setItems] = useState(products)
  const [count, setCount] = useState(products.length) // Derived!

  // Bug: count can get out of sync with items
}

// Correct: Compute derived values
function ProductList({ products }) {
  const [items, setItems] = useState(products)
  const count = items.length // Always in sync
}
URL State for Filters/Pagination
typescript
'use client'

import { useSearchParams, useRouter } from 'next/navigation'

function ProductFilters() {
  const searchParams = useSearchParams()
  const router = useRouter()

  const category = searchParams.get('category') || 'all'

  function setCategory(newCategory: string) {
    const params = new URLSearchParams(searchParams)
    params.set('category', newCategory)
    router.push(`?${params.toString()}`)
  }

  return (
    <Select value={category} onValueChange={setCategory}>
      {/* options */}
    </Select>
  )
}

避免存储衍生状态
typescript
// 错误写法:存储衍生状态
function ProductList({ products }) {
  const [items, setItems] = useState(products)
  const [count, setCount] = useState(products.length) // 衍生值!

  // 问题:count可能和items不同步
}

// 正确写法:直接计算衍生值
function ProductList({ products }) {
  const [items, setItems] = useState(products)
  const count = items.length // 永远同步
}
筛选/分页状态存储在URL中
typescript
'use client'

import { useSearchParams, useRouter } from 'next/navigation'

function ProductFilters() {
  const searchParams = useSearchParams()
  const router = useRouter()

  const category = searchParams.get('category') || 'all'

  function setCategory(newCategory: string) {
    const params = new URLSearchParams(searchParams)
    params.set('category', newCategory)
    router.push(`?${params.toString()}`)
  }

  return (
    <Select value={category} onValueChange={setCategory}>
      {/* 选项 */}
    </Select>
  )
}

7. Motion & Animations (MEDIUM)

7. Motion与动画实现(中优先级)

Quick Reference

快速参考

  • motion-purposeful
    - Animations should enhance UX, not distract
  • motion-performance
    - Use transform/opacity, avoid layout triggers
  • motion-reduced-motion
    - Respect prefers-reduced-motion
  • motion-layout-id
    - Use layoutId for shared element transitions
  • motion-exit-animations
    - Use AnimatePresence for exit animations
  • motion-variants
    - Define reusable animation states
  • motion-purposeful
    - 动画应提升UX而非干扰用户
  • motion-performance
    - 使用transform/opacity,避免触发布局重排
  • motion-reduced-motion
    - 尊重用户的减少动画偏好设置
  • motion-layout-id
    - 共享元素过渡使用layoutId
  • motion-exit-animations
    - 离场动画使用AnimatePresence
  • motion-variants
    - 定义可复用的动画状态

Installation

安装

bash
npm install motion
bash
npm install motion

Core Principles

核心原则

Motion (formerly Framer Motion) provides declarative animations that enhance user experience.
Basic Animations
typescript
'use client'

import { motion } from 'motion/react'

function FadeInCard({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
      className="rounded-lg border p-4"
    >
      {children}
    </motion.div>
  )
}
Interaction States
typescript
function InteractiveButton({ children }: { children: React.ReactNode }) {
  return (
    <motion.button
      whileHover={{ scale: 1.02 }}
      whileTap={{ scale: 0.98 }}
      transition={{ type: 'spring', stiffness: 400, damping: 17 }}
      className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
    >
      {children}
    </motion.button>
  )
}
Exit Animations with AnimatePresence
typescript
import { motion, AnimatePresence } from 'motion/react'

function NotificationList({ notifications }: { notifications: Notification[] }) {
  return (
    <AnimatePresence>
      {notifications.map((notification) => (
        <motion.div
          key={notification.id}
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: -50 }}
          transition={{ duration: 0.2 }}
        >
          <Notification data={notification} />
        </motion.div>
      ))}
    </AnimatePresence>
  )
}
Shared Element Transitions
typescript
function ProductGrid({ products }: { products: Product[] }) {
  const [selected, setSelected] = useState<Product | null>(null)

  return (
    <>
      <div className="grid grid-cols-3 gap-4">
        {products.map((product) => (
          <motion.div
            key={product.id}
            layoutId={`product-${product.id}`}
            onClick={() => setSelected(product)}
            className="cursor-pointer"
          >
            <img src={product.image} alt={product.name} />
          </motion.div>
        ))}
      </div>

      <AnimatePresence>
        {selected && (
          <motion.div
            layoutId={`product-${selected.id}`}
            className="fixed inset-0 flex items-center justify-center"
          >
            <ProductDetail product={selected} onClose={() => setSelected(null)} />
          </motion.div>
        )}
      </AnimatePresence>
    </>
  )
}
Reusable Variants
typescript
const fadeInUp = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -20 },
}

const staggerContainer = {
  animate: {
    transition: {
      staggerChildren: 0.1,
    },
  },
}

function StaggeredList({ items }: { items: string[] }) {
  return (
    <motion.ul variants={staggerContainer} initial="initial" animate="animate">
      {items.map((item, i) => (
        <motion.li key={i} variants={fadeInUp}>
          {item}
        </motion.li>
      ))}
    </motion.ul>
  )
}
Scroll-Triggered Animations
typescript
import { motion, useInView } from 'motion/react'
import { useRef } from 'react'

function ScrollReveal({ children }: { children: React.ReactNode }) {
  const ref = useRef(null)
  const isInView = useInView(ref, { once: true, margin: '-100px' })

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  )
}
Respecting Reduced Motion
typescript
import { motion, useReducedMotion } from 'motion/react'

function AccessibleAnimation({ children }: { children: React.ReactNode }) {
  const shouldReduceMotion = useReducedMotion()

  return (
    <motion.div
      initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
    >
      {children}
    </motion.div>
  )
}
Motion(原Framer Motion)提供声明式动画,可提升用户体验。
基础动画
typescript
'use client'

import { motion } from 'motion/react'

function FadeInCard({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
      className="rounded-lg border p-4"
    >
      {children}
    </motion.div>
  )
}
交互状态
typescript
function InteractiveButton({ children }: { children: React.ReactNode }) {
  return (
    <motion.button
      whileHover={{ scale: 1.02 }}
      whileTap={{ scale: 0.98 }}
      transition={{ type: 'spring', stiffness: 400, damping: 17 }}
      className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
    >
      {children}
    </motion.button>
  )
}
使用AnimatePresence实现离场动画
typescript
import { motion, AnimatePresence } from 'motion/react'

function NotificationList({ notifications }: { notifications: Notification[] }) {
  return (
    <AnimatePresence>
      {notifications.map((notification) => (
        <motion.div
          key={notification.id}
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: -50 }}
          transition={{ duration: 0.2 }}
        >
          <Notification data={notification} />
        </motion.div>
      ))}
    </AnimatePresence>
  )
}
共享元素过渡
typescript
function ProductGrid({ products }: { products: Product[] }) {
  const [selected, setSelected] = useState<Product | null>(null)

  return (
    <>
      <div className="grid grid-cols-3 gap-4">
        {products.map((product) => (
          <motion.div
            key={product.id}
            layoutId={`product-${product.id}`}
            onClick={() => setSelected(product)}
            className="cursor-pointer"
          >
            <img src={product.image} alt={product.name} />
          </motion.div>
        ))}
      </div>

      <AnimatePresence>
        {selected && (
          <motion.div
            layoutId={`product-${selected.id}`}
            className="fixed inset-0 flex items-center justify-center"
          >
            <ProductDetail product={selected} onClose={() => setSelected(null)} />
          </motion.div>
        )}
      </AnimatePresence>
    </>
  )
}
可复用变体
typescript
const fadeInUp = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -20 },
}

const staggerContainer = {
  animate: {
    transition: {
      staggerChildren: 0.1,
    },
  },
}

function StaggeredList({ items }: { items: string[] }) {
  return (
    <motion.ul variants={staggerContainer} initial="initial" animate="animate">
      {items.map((item, i) => (
        <motion.li key={i} variants={fadeInUp}>
          {item}
        </motion.li>
      ))}
    </motion.ul>
  )
}
滚动触发动画
typescript
import { motion, useInView } from 'motion/react'
import { useRef } from 'react'

function ScrollReveal({ children }: { children: React.ReactNode }) {
  const ref = useRef(null)
  const isInView = useInView(ref, { once: true, margin: '-100px' })

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  )
}
尊重减少动画偏好
typescript
import { motion, useReducedMotion } from 'motion/react'

function AccessibleAnimation({ children }: { children: React.ReactNode }) {
  const shouldReduceMotion = useReducedMotion()

  return (
    <motion.div
      initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
    >
      {children}
    </motion.div>
  )
}

Performance Tips

性能提示

  • Use
    transform
    and
    opacity
    for smooth 60fps animations
  • Set
    willChange
    prop for complex animations
  • Keep exit animations short (under 300ms)
  • Use
    useInView
    to lazy-load animations
  • Avoid animating
    width
    ,
    height
    ,
    top
    ,
    left
    directly

  • 使用
    transform
    opacity
    实现流畅的60fps动画
  • 复杂动画设置
    willChange
    属性
  • 离场动画时长控制在300ms以内
  • 使用
    useInView
    懒加载动画
  • 避免直接动画
    width
    height
    top
    left
    属性

8. Re-render Optimization (MEDIUM)

8. 重渲染优化(中优先级)

Quick Reference

快速参考

  • rerender-memo
    - Extract expensive work into memoized components
  • rerender-usememo
    - Memoize expensive calculations
  • rerender-usecallback
    - Stabilize callback references
  • rerender-dependencies
    - Use primitive dependencies in effects
  • rerender-transitions
    - Use startTransition for non-urgent updates
  • rerender-memo
    - 昂贵渲染逻辑提取到memo包裹的组件中
  • rerender-usememo
    - 昂贵计算使用useMemo缓存
  • rerender-usecallback
    - 稳定回调引用使用useCallback
  • rerender-dependencies
    - effect中使用基础类型依赖
  • rerender-transitions
    - 非紧急更新使用startTransition

Key Principles

核心原则

Memoization for Expensive Components
typescript
import { memo } from 'react'

const ExpensiveList = memo(function ExpensiveList({
  items
}: {
  items: Item[]
}) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{/* expensive render */}</li>
      ))}
    </ul>
  )
})
Stable Callbacks
typescript
function Parent() {
  const [count, setCount] = useState(0)

  // Stable reference - won't cause child re-renders
  const handleClick = useCallback(() => {
    setCount(c => c + 1)
  }, [])

  return <MemoizedChild onClick={handleClick} />
}
Non-Urgent Updates with Transitions
typescript
function SearchResults() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [isPending, startTransition] = useTransition()

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setQuery(e.target.value) // Urgent: update input immediately

    startTransition(() => {
      setResults(filterResults(e.target.value)) // Non-urgent: can be interrupted
    })
  }

  return (
    <>
      <Input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </>
  )
}

昂贵组件使用Memo缓存
typescript
import { memo } from 'react'

const ExpensiveList = memo(function ExpensiveList({
  items
}: {
  items: Item[]
}) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{/* 昂贵渲染逻辑 */}</li>
      ))}
    </ul>
  )
})
稳定回调引用
typescript
function Parent() {
  const [count, setCount] = useState(0)

  // 稳定引用 - 不会导致子组件重渲染
  const handleClick = useCallback(() => {
    setCount(c => c + 1)
  }, [])

  return <MemoizedChild onClick={handleClick} />
}
非紧急更新使用Transition
typescript
function SearchResults() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [isPending, startTransition] = useTransition()

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setQuery(e.target.value) // 紧急更新:立即更新输入框

    startTransition(() => {
      setResults(filterResults(e.target.value)) // 非紧急更新:可被中断
    })
  }

  return (
    <>
      <Input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </>
  )
}

9. Accessibility (MEDIUM)

9. 可访问性(中优先级)

Quick Reference

快速参考

  • a11y-semantic-html
    - Use correct HTML elements
  • a11y-keyboard-nav
    - Ensure keyboard navigability
  • a11y-aria-labels
    - Add descriptive labels for screen readers
  • a11y-focus-management
    - Manage focus in modals and dynamic content
  • a11y-color-contrast
    - Ensure sufficient color contrast
  • a11y-semantic-html
    - 使用正确的HTML元素
  • a11y-keyboard-nav
    - 确保支持键盘导航
  • a11y-aria-labels
    - 为屏幕阅读器添加描述性标签
  • a11y-focus-management
    - 弹窗和动态内容中管理焦点
  • a11y-color-contrast
    - 确保足够的颜色对比度

Key Principles

核心原则

Semantic HTML
typescript
// Incorrect: div soup
<div onClick={handleClick}>Click me</div>

// Correct: semantic button
<button onClick={handleClick}>Click me</button>
Focus Management in Modals
typescript
function Modal({ isOpen, onClose, children }) {
  const closeButtonRef = useRef<HTMLButtonElement>(null)

  useEffect(() => {
    if (isOpen) {
      closeButtonRef.current?.focus()
    }
  }, [isOpen])

  return (
    <Dialog open={isOpen} onOpenChange={onClose}>
      <DialogContent>
        {children}
        <Button ref={closeButtonRef} onClick={onClose}>
          Close
        </Button>
      </DialogContent>
    </Dialog>
  )
}
Skip Links
typescript
function Layout({ children }) {
  return (
    <>
      <a
        href="#main-content"
        className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4"
      >
        Skip to main content
      </a>
      <Header />
      <main id="main-content">{children}</main>
    </>
  )
}

语义化HTML
typescript
// 错误写法:无意义div
<div onClick={handleClick}>点击我</div>

// 正确写法:语义化button
<button onClick={handleClick}>点击我</button>
弹窗中管理焦点
typescript
function Modal({ isOpen, onClose, children }) {
  const closeButtonRef = useRef<HTMLButtonElement>(null)

  useEffect(() => {
    if (isOpen) {
      closeButtonRef.current?.focus()
    }
  }, [isOpen])

  return (
    <Dialog open={isOpen} onOpenChange={onClose}>
      <DialogContent>
        {children}
        <Button ref={closeButtonRef} onClick={onClose}>
          关闭
        </Button>
      </DialogContent>
    </Dialog>
  )
}
跳转链接
typescript
function Layout({ children }) {
  return (
    <>
      <a
        href="#main-content"
        className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4"
      >
        跳转到主内容
      </a>
      <Header />
      <main id="main-content">{children}</main>
    </>
  )
}

10. TypeScript Patterns (MEDIUM)

10. TypeScript设计模式(中优先级)

Quick Reference

快速参考

  • ts-strict-mode
    - Enable strict TypeScript configuration
  • ts-component-props
    - Define explicit prop interfaces
  • ts-generics
    - Use generics for reusable components
  • ts-discriminated-unions
    - Use for state machines
  • ts-infer-when-possible
    - Let TypeScript infer when obvious
  • ts-strict-mode
    - 启用TypeScript严格模式配置
  • ts-component-props
    - 定义明确的props接口
  • ts-generics
    - 可复用组件使用泛型
  • ts-discriminated-unions
    - 状态机使用可辨识联合
  • ts-infer-when-possible
    - 类型明确时让TypeScript自动推断

Key Principles

核心原则

Component Props
typescript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'destructive' | 'outline'
  size?: 'sm' | 'md' | 'lg'
  isLoading?: boolean
}

function Button({
  variant = 'default',
  size = 'md',
  isLoading,
  children,
  disabled,
  ...props
}: ButtonProps) {
  return (
    <button
      disabled={disabled || isLoading}
      {...props}
    >
      {isLoading ? <Spinner /> : children}
    </button>
  )
}
Discriminated Unions for State
typescript
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

function useAsync<T>(asyncFn: () => Promise<T>) {
  const [state, setState] = useState<AsyncState<T>>({ status: 'idle' })

  // TypeScript knows exact shape based on status
  if (state.status === 'success') {
    return state.data // TypeScript knows data exists
  }
}
Generic Components
typescript
interface SelectProps<T> {
  options: T[]
  value: T
  onChange: (value: T) => void
  getLabel: (option: T) => string
  getValue: (option: T) => string
}

function Select<T>({ options, value, onChange, getLabel, getValue }: SelectProps<T>) {
  return (
    <select
      value={getValue(value)}
      onChange={(e) => {
        const selected = options.find(o => getValue(o) === e.target.value)
        if (selected) onChange(selected)
      }}
    >
      {options.map((option) => (
        <option key={getValue(option)} value={getValue(option)}>
          {getLabel(option)}
        </option>
      ))}
    </select>
  )
}

组件Props定义
typescript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'destructive' | 'outline'
  size?: 'sm' | 'md' | 'lg'
  isLoading?: boolean
}

function Button({
  variant = 'default',
  size = 'md',
  isLoading,
  children,
  disabled,
  ...props
}: ButtonProps) {
  return (
    <button
      disabled={disabled || isLoading}
      {...props}
    >
      {isLoading ? <Spinner /> : children}
    </button>
  )
}
状态使用可辨识联合
typescript
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

function useAsync<T>(asyncFn: () => Promise<T>) {
  const [state, setState] = useState<AsyncState<T>>({ status: 'idle' })

  // TypeScript会根据status判断准确的类型结构
  if (state.status === 'success') {
    return state.data // TypeScript知道data一定存在
  }
}
泛型组件
typescript
interface SelectProps<T> {
  options: T[]
  value: T
  onChange: (value: T) => void
  getLabel: (option: T) => string
  getValue: (option: T) => string
}

function Select<T>({ options, value, onChange, getLabel, getValue }: SelectProps<T>) {
  return (
    <select
      value={getValue(value)}
      onChange={(e) => {
        const selected = options.find(o => getValue(o) === e.target.value)
        if (selected) onChange(selected)
      }}
    >
      {options.map((option) => (
        <option key={getValue(option)} value={getValue(option)}>
          {getLabel(option)}
        </option>
      ))}
    </select>
  )
}

React 19+ Features

React 19+ 新特性

New Hooks

新增Hook

useActionState - Form state management
typescript
'use client'

import { useActionState } from 'react'

function SubscribeForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      const email = formData.get('email')
      const result = await subscribe(email)
      return result
    },
    null
  )

  return (
    <form action={formAction}>
      <Input name="email" type="email" />
      <Button type="submit" disabled={isPending}>
        {isPending ? 'Subscribing...' : 'Subscribe'}
      </Button>
      {state?.error && <p className="text-red-500">{state.error}</p>}
    </form>
  )
}
useOptimistic - Optimistic UI updates
typescript
'use client'

import { useOptimistic } from 'react'

function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: Todo) => [...state, newTodo]
  )

  async function addTodo(formData: FormData) {
    const title = formData.get('title') as string
    const newTodo = { id: crypto.randomUUID(), title, completed: false }

    addOptimisticTodo(newTodo) // Immediately show in UI
    await createTodo(title)    // Then persist to server
  }

  return (
    <>
      <form action={addTodo}>
        <Input name="title" />
        <Button type="submit">Add</Button>
      </form>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </>
  )
}
use - Async resource reading
typescript
import { use, Suspense } from 'react'

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise) // Suspends until resolved
  return <div>{user.name}</div>
}

function Page({ userId }: { userId: string }) {
  const userPromise = fetchUser(userId)

  return (
    <Suspense fallback={<UserSkeleton />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  )
}

useActionState - 表单状态管理
typescript
'use client'

import { useActionState } from 'react'

function SubscribeForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      const email = formData.get('email')
      const result = await subscribe(email)
      return result
    },
    null
  )

  return (
    <form action={formAction}>
      <Input name="email" type="email" />
      <Button type="submit" disabled={isPending}>
        {isPending ? '订阅中...' : '订阅'}
      </Button>
      {state?.error && <p className="text-red-500">{state.error}</p>}
    </form>
  )
}
useOptimistic - 乐观UI更新
typescript
'use client'

import { useOptimistic } from 'react'

function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: Todo) => [...state, newTodo]
  )

  async function addTodo(formData: FormData) {
    const title = formData.get('title') as string
    const newTodo = { id: crypto.randomUUID(), title, completed: false }

    addOptimisticTodo(newTodo) // 立即在UI中展示
    await createTodo(title)    // 之后再持久化到服务端
  }

  return (
    <>
      <form action={addTodo}>
        <Input name="title" />
        <Button type="submit">添加</Button>
      </form>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </>
  )
}
use - 异步资源读取
typescript
import { use, Suspense } from 'react'

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise) // 挂起直到资源加载完成
  return <div>{user.name}</div>
}

function Page({ userId }: { userId: string }) {
  const userPromise = fetchUser(userId)

  return (
    <Suspense fallback={<UserSkeleton />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  )
}

Project Structure

项目结构

src/
├── app/                    # Next.js App Router
│   ├── layout.tsx
│   ├── page.tsx
│   └── (routes)/
├── components/
│   ├── ui/                 # shadcn/ui components
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   └── ...
│   └── features/           # Feature-specific components
│       ├── auth/
│       └── dashboard/
├── hooks/                  # Custom hooks
├── lib/                    # Utilities
│   ├── utils.ts           # cn() helper, etc.
│   └── validations.ts     # Zod schemas
├── actions/               # Server Actions
└── types/                 # TypeScript types

src/
├── app/                    # Next.js App Router
│   ├── layout.tsx
│   ├── page.tsx
│   └── (routes)/
├── components/
│   ├── ui/                 # shadcn/ui 组件
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   └── ...
│   └── features/           # 业务功能专属组件
│       ├── auth/
│       └── dashboard/
├── hooks/                  # 自定义Hook
├── lib/                    # 工具函数
│   ├── utils.ts           # cn() 辅助函数等
│   └── validations.ts     # Zod 校验 schema
├── actions/               # Server Actions
└── types/                 # TypeScript 类型定义

References

参考资料