react-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Development Patterns

React开发模式

Expert guide for building modern React 19 applications with new concurrent features, Server Components, Actions, and advanced patterns.
基于React 19新并发特性、Server Components、Actions及高级模式构建现代应用的专家指南。

When to Use

适用场景

  • Building React 19 components with TypeScript/JavaScript
  • Managing component state with useState and useReducer
  • Handling side effects with useEffect
  • Optimizing performance with useMemo and useCallback
  • Creating custom hooks for reusable logic
  • Implementing component composition patterns
  • Working with refs using useRef
  • Using React 19's new features (use(), useOptimistic, useFormStatus)
  • Implementing Server Components and Actions
  • Working with Suspense and concurrent rendering
  • Building forms with new form hooks
  • 使用TypeScript/JavaScript构建React 19组件
  • 使用useState和useReducer管理组件状态
  • 使用useEffect处理副作用
  • 使用useMemo和useCallback优化性能
  • 创建自定义钩子实现可复用逻辑
  • 实现组件组合模式
  • 使用useRef处理引用
  • 使用React 19新特性(use()、useOptimistic、useFormStatus)
  • 实现Server Components和Actions
  • 使用Suspense和并发渲染
  • 使用新表单钩子构建表单

Core Hooks Patterns

核心钩子模式

useState - State Management

useState - 状态管理

Basic state declaration and updates:
typescript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
State with initializer function (expensive computation):
typescript
const [state, setState] = useState(() => {
  const initialState = computeExpensiveValue();
  return initialState;
});
Multiple state variables:
typescript
function UserProfile() {
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const [email, setEmail] = useState('');
  
  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}
基本状态声明与更新:
typescript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
带初始化函数的状态(昂贵计算场景):
typescript
const [state, setState] = useState(() => {
  const initialState = computeExpensiveValue();
  return initialState;
});
多状态变量:
typescript
function UserProfile() {
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const [email, setEmail] = useState('');
  
  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}

useEffect - Side Effects

useEffect - 副作用处理

Basic effect with cleanup:
typescript
import { useEffect } from 'react';

function ChatRoom({ roomId }: { roomId: string }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    // Cleanup function
    return () => {
      connection.disconnect();
    };
  }, [roomId]); // Dependency array
  
  return <div>Connected to {roomId}</div>;
}
Effect with multiple dependencies:
typescript
function ChatRoom({ roomId, serverUrl }: { roomId: string; serverUrl: string }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    
    return () => connection.disconnect();
  }, [roomId, serverUrl]); // Re-run when either changes
  
  return <h1>Welcome to {roomId}</h1>;
}
Effect for subscriptions:
typescript
function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    
    function handleOffline() {
      setIsOnline(false);
    }
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []); // Empty array = run once on mount
  
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
带清理逻辑的基础副作用:
typescript
import { useEffect } from 'react';

function ChatRoom({ roomId }: { roomId: string }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    // 清理函数
    return () => {
      connection.disconnect();
    };
  }, [roomId]); // 依赖数组
  
  return <div>已连接到 {roomId}</div>;
}
多依赖的副作用:
typescript
function ChatRoom({ roomId, serverUrl }: { roomId: string; serverUrl: string }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    
    return () => connection.disconnect();
  }, [roomId, serverUrl]); // 任一依赖变更时重新执行
  
  return <h1>欢迎来到 {roomId}</h1>;
}
订阅类副作用:
typescript
function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    
    function handleOffline() {
      setIsOnline(false);
    }
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []); // 空数组 = 仅在挂载时执行一次
  
  return <h1>{isOnline ? '✅ 在线' : '❌ 已断开'}</h1>;
}

useRef - Persistent References

useRef - 持久化引用

Storing mutable values without re-renders:
typescript
import { useRef } from 'react';

function Timer() {
  const intervalRef = useRef<NodeJS.Timeout | null>(null);
  
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      console.log('Tick');
    }, 1000);
  };
  
  const stopTimer = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
  };
  
  return (
    <>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </>
  );
}
DOM element references:
typescript
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const focusInput = () => {
    inputRef.current?.focus();
  };
  
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </>
  );
}
存储可变值且不触发重渲染:
typescript
import { useRef } from 'react';

function Timer() {
  const intervalRef = useRef<NodeJS.Timeout | null>(null);
  
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      console.log('Tick');
    }, 1000);
  };
  
  const stopTimer = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
  };
  
  return (
    <>
      <button onClick={startTimer}>开始</button>
      <button onClick={stopTimer}>停止</button>
    </>
  );
}
DOM元素引用:
typescript
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const focusInput = () => {
    inputRef.current?.focus();
  };
  
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </>
  );
}

Custom Hooks Pattern

自定义钩子模式

Extract reusable logic into custom hooks:
typescript
// useOnlineStatus.ts
import { useState, useEffect } from 'react';

export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    
    function handleOffline() {
      setIsOnline(false);
    }
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}

// Usage in components
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();
  return (
    <button disabled={!isOnline}>
      {isOnline ? 'Save' : 'Reconnecting...'}
    </button>
  );
}
Custom hook with parameters:
typescript
// useChatRoom.ts
import { useEffect } from 'react';

interface ChatOptions {
  serverUrl: string;
  roomId: string;
}

export function useChatRoom({ serverUrl, roomId }: ChatOptions) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    
    return () => connection.disconnect();
  }, [serverUrl, roomId]);
}

// Usage
function ChatRoom({ roomId }: { roomId: string }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  
  useChatRoom({ serverUrl, roomId });
  
  return (
    <>
      <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} />
      <h1>Welcome to {roomId}</h1>
    </>
  );
}
将可复用逻辑提取为自定义钩子:
typescript
// useOnlineStatus.ts
import { useState, useEffect } from 'react';

export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    
    function handleOffline() {
      setIsOnline(false);
    }
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}

// 在组件中使用
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ 在线' : '❌ 已断开'}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();
  return (
    <button disabled={!isOnline}>
      {isOnline ? '保存' : '重新连接中...'}
    </button>
  );
}
带参数的自定义钩子:
typescript
// useChatRoom.ts
import { useEffect } from 'react';

interface ChatOptions {
  serverUrl: string;
  roomId: string;
}

export function useChatRoom({ serverUrl, roomId }: ChatOptions) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    
    return () => connection.disconnect();
  }, [serverUrl, roomId]);
}

// 使用示例
function ChatRoom({ roomId }: { roomId: string }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  
  useChatRoom({ serverUrl, roomId });
  
  return (
    <>
      <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} />
      <h1>欢迎来到 {roomId}</h1>
    </>
  );
}

Component Composition Patterns

组件组合模式

Props and Children

Props与Children

Basic component with props:
typescript
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  onClick?: () => void;
  children: React.ReactNode;
}

function Button({ variant = 'primary', size = 'md', onClick, children }: ButtonProps) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}
Composition with children:
typescript
interface CardProps {
  children: React.ReactNode;
  className?: string;
}

function Card({ children, className = '' }: CardProps) {
  return (
    <div className={`card ${className}`}>
      {children}
    </div>
  );
}

// Usage
function UserProfile() {
  return (
    <Card>
      <h2>John Doe</h2>
      <p>Software Engineer</p>
    </Card>
  );
}
带Props的基础组件:
typescript
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  onClick?: () => void;
  children: React.ReactNode;
}

function Button({ variant = 'primary', size = 'md', onClick, children }: ButtonProps) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}
使用Children的组合:
typescript
interface CardProps {
  children: React.ReactNode;
  className?: string;
}

function Card({ children, className = '' }: CardProps) {
  return (
    <div className={`card ${className}`}>
      {children}
    </div>
  );
}

// 使用示例
function UserProfile() {
  return (
    <Card>
      <h2>John Doe</h2>
      <p>软件工程师</p>
    </Card>
  );
}

Lifting State Up

状态提升

Shared state between siblings:
typescript
function Parent() {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <>
      <Panel
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        Panel 1 content
      </Panel>
      <Panel
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        Panel 2 content
      </Panel>
    </>
  );
}

interface PanelProps {
  isActive: boolean;
  onShow: () => void;
  children: React.ReactNode;
}

function Panel({ isActive, onShow, children }: PanelProps) {
  return (
    <div>
      <button onClick={onShow}>Show</button>
      {isActive && <div>{children}</div>}
    </div>
  );
}
兄弟组件间共享状态:
typescript
function Parent() {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <>
      <Panel
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        面板1内容
      </Panel>
      <Panel
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        面板2内容
      </Panel>
    </>
  );
}

interface PanelProps {
  isActive: boolean;
  onShow: () => void;
  children: React.ReactNode;
}

function Panel({ isActive, onShow, children }: PanelProps) {
  return (
    <div>
      <button onClick={onShow}>显示</button>
      {isActive && <div>{children}</div>}
    </div>
  );
}

Performance Optimization

性能优化

Avoid Unnecessary Effects

避免不必要的副作用

❌ Bad - Using effect for derived state:
typescript
function TodoList({ todos }: { todos: Todo[] }) {
  const [visibleTodos, setVisibleTodos] = useState<Todo[]>([]);
  
  useEffect(() => {
    setVisibleTodos(todos.filter(t => !t.completed));
  }, [todos]); // Unnecessary effect
  
  return <ul>{/* ... */}</ul>;
}
✅ Good - Compute during render:
typescript
function TodoList({ todos }: { todos: Todo[] }) {
  const visibleTodos = todos.filter(t => !t.completed); // Direct computation
  
  return <ul>{/* ... */}</ul>;
}
❌ 错误示例 - 使用副作用处理派生状态:
typescript
function TodoList({ todos }: { todos: Todo[] }) {
  const [visibleTodos, setVisibleTodos] = useState<Todo[]>([]);
  
  useEffect(() => {
    setVisibleTodos(todos.filter(t => !t.completed));
  }, [todos]); // 不必要的副作用
  
  return <ul>{/* ... */}</ul>;
}
✅ 正确示例 - 在渲染时直接计算:
typescript
function TodoList({ todos }: { todos: Todo[] }) {
  const visibleTodos = todos.filter(t => !t.completed); // 直接计算
  
  return <ul>{/* ... */}</ul>;
}

useMemo for Expensive Computations

useMemo处理昂贵计算

typescript
import { useMemo } from 'react';

function DataTable({ data }: { data: Item[] }) {
  const sortedData = useMemo(() => {
    return [...data].sort((a, b) => a.name.localeCompare(b.name));
  }, [data]); // Only recompute when data changes
  
  return <table>{/* render sortedData */}</table>;
}
typescript
import { useMemo } from 'react';

function DataTable({ data }: { data: Item[] }) {
  const sortedData = useMemo(() => {
    return [...data].sort((a, b) => a.name.localeCompare(b.name));
  }, [data]); // 仅当data变更时重新计算
  
  return <table>{/* 渲染sortedData */}</table>;
}

useCallback for Function Stability

useCallback保证函数稳定性

typescript
import { useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Clicked', count);
  }, [count]); // Recreate only when count changes
  
  return <ExpensiveChild onClick={handleClick} />;
}
typescript
import { useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('已点击', count);
  }, [count]); // 仅当count变更时重新创建函数
  
  return <ExpensiveChild onClick={handleClick} />;
}

TypeScript Best Practices

TypeScript最佳实践

Type-Safe Props

类型安全的Props

typescript
interface UserProps {
  id: string;
  name: string;
  email: string;
  age?: number; // Optional
}

function User({ id, name, email, age }: UserProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      {age && <p>Age: {age}</p>}
    </div>
  );
}
typescript
interface UserProps {
  id: string;
  name: string;
  email: string;
  age?: number; // 可选属性
}

function User({ id, name, email, age }: UserProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      {age && <p>年龄: {age}</p>}
    </div>
  );
}

Generic Components

泛型组件

typescript
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Usage
<List 
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
/>
typescript
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 使用示例
<List 
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
/>

Event Handlers

事件处理器

typescript
function Form() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // Handle form submission
  };
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
    </form>
  );
}
typescript
function Form() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // 处理表单提交
  };
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
    </form>
  );
}

Common Patterns

常见模式

Controlled Components

受控组件

typescript
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input 
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}
typescript
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input 
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

Conditional Rendering

条件渲染

typescript
function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
  return (
    <div>
      {isLoggedIn ? (
        <UserGreeting />
      ) : (
        <GuestGreeting />
      )}
    </div>
  );
}
typescript
function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
  return (
    <div>
      {isLoggedIn ? (
        <UserGreeting />
      ) : (
        <GuestGreeting />
      )}
    </div>
  );
}

Lists and Keys

列表与Keys

typescript
function UserList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}
typescript
function UserList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

Best Practices

最佳实践

General React Best Practices

通用React最佳实践

  1. Dependency Arrays: Always specify correct dependencies in useEffect
  2. State Structure: Keep state minimal and avoid redundant state
  3. Component Size: Keep components small and focused
  4. Custom Hooks: Extract complex logic into reusable custom hooks
  5. TypeScript: Use TypeScript for type safety
  6. Keys: Use stable IDs as keys for list items, not array indices
  7. Immutability: Never mutate state directly
  8. Effects: Use effects only for synchronization with external systems
  9. Performance: Profile before optimizing with useMemo/useCallback
  1. 依赖数组:始终在useEffect中指定正确的依赖
  2. 状态结构:保持状态最小化,避免冗余状态
  3. 组件大小:保持组件小巧且聚焦单一职责
  4. 自定义钩子:将复杂逻辑提取为可复用的自定义钩子
  5. TypeScript:使用TypeScript保证类型安全
  6. Keys:使用稳定ID作为列表项的key,而非数组索引
  7. 不可变性:永远不要直接修改状态
  8. 副作用:仅在与外部系统同步时使用副作用
  9. 性能:先分析性能瓶颈,再使用useMemo/useCallback进行优化

React 19 Specific Best Practices

React 19特定最佳实践

  1. Server Components: Use Server Components for data fetching and static content
  2. Client Components: Mark components as 'use client' only when necessary
  3. Actions: Use Server Actions for mutations and form submissions
  4. Optimistic Updates: Implement useOptimistic for better UX
  5. use() Hook: Use for reading promises and context conditionally
  6. Form State: Use useFormState and useFormStatus for complex forms
  7. Concurrent Features: Leverage useTransition for non-urgent updates
  8. Error Boundaries: Implement proper error handling with error boundaries
  1. Server Components:使用Server Components处理数据获取和静态内容
  2. Client Components:仅在必要时将组件标记为'use client'
  3. Actions:使用Server Actions处理突变和表单提交
  4. 乐观更新:使用useOptimistic实现更好的用户体验
  5. use()钩子:用于条件性读取Promise和Context
  6. 表单状态:使用useFormState和useFormStatus处理复杂表单
  7. 并发特性:利用useTransition处理非紧急更新
  8. 错误边界:使用错误边界实现完善的错误处理

Common Pitfalls

常见陷阱

General React Pitfalls

通用React陷阱

Missing Dependencies:
typescript
useEffect(() => {
  // Uses 'count' but doesn't include it in deps
  console.log(count);
}, []); // Wrong!
Mutating State:
typescript
const [items, setItems] = useState([]);
items.push(newItem); // Wrong! Mutates state
setItems(items); // Won't trigger re-render
Correct Approach:
typescript
setItems([...items, newItem]); // Create new array
缺失依赖
typescript
useEffect(() => {
  // 使用了'count'但未将其加入依赖
  console.log(count);
}, []); // 错误!
直接修改状态
typescript
const [items, setItems] = useState([]);
items.push(newItem); // 错误!直接修改状态
setItems(items); // 不会触发重渲染
正确做法
typescript
setItems([...items, newItem]); // 创建新数组

React 19 Specific Pitfalls

React 19特定陷阱

Using use() outside of render:
typescript
// Wrong!
function handleClick() {
  const data = use(promise); // Error: use() can only be called in render
}
Correct usage:
tsx
function Component({ promise }) {
  const data = use(promise); // Correct: called during render
  return <div>{data}</div>;
}
Forgetting 'use server' directive:
typescript
// Wrong - missing 'use server'
export async function myAction() {
  // This will run on the client!
}
Correct Server Action:
typescript
'use server'; // Must be at the top

export async function myAction() {
  // Now runs on the server
}
Mixing Server and Client logic incorrectly:
tsx
// Wrong - trying to use browser APIs in Server Component
export default async function ServerComponent() {
  const width = window.innerWidth; // Error: window is not defined
  return <div>{width}</div>;
}
Correct separation:
tsx
// Server Component for data
export default async function ServerComponent() {
  const data = await fetchData();
  return <ClientComponent data={data} />;
}

// Client Component for browser APIs
'use client';

function ClientComponent({ data }) {
  const [width, setWidth] = useState(window.innerWidth);
  // Handle resize logic...
  return <div>{width}</div>;
}
在渲染外部使用use()
typescript
// 错误!
function handleClick() {
  const data = use(promise); // 错误:use()只能在渲染时调用
}
正确用法
tsx
function Component({ promise }) {
  const data = use(promise); // 正确:在渲染时调用
  return <div>{data}</div>;
}
忘记'use server'指令
typescript
// 错误 - 缺少'use server'
export async function myAction() {
  // 这会在客户端运行!
}
正确的Server Action
typescript
'use server'; // 必须放在顶部

export async function myAction() {
  // 现在在服务器端运行
}
错误混合Server与Client逻辑
tsx
// 错误 - 在Server Component中尝试使用浏览器API
export default async function ServerComponent() {
  const width = window.innerWidth; // 错误:window未定义
  return <div>{width}</div>;
}
正确的分离方式
tsx
// 用于数据获取的Server Component
export default async function ServerComponent() {
  const data = await fetchData();
  return <ClientComponent data={data} />;
}

// 用于浏览器API的Client Component
'use client';

function ClientComponent({ data }) {
  const [width, setWidth] = useState(window.innerWidth);
  // 处理 resize 逻辑...
  return <div>{width}</div>;
}

React 19 New Features

React 19新特性

use() Hook - Reading Resources

use()钩子 - 读取资源

The
use()
hook reads the value from a resource like a Promise or Context:
typescript
import { use } from 'react';

// Reading a Promise in a component
function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  return <p>{message}</p>;
}

// Reading Context conditionally
function Button() {
  if (condition) {
    const theme = use(ThemeContext);
    return <button className={theme}>Click</button>;
  }
  return <button>Click</button>;
}
use()
钩子用于读取Promise或Context等资源的值:
typescript
import { use } from 'react';

// 在组件中读取Promise
function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  return <p>{message}</p>;
}

// 条件性读取Context
function Button() {
  if (condition) {
    const theme = use(ThemeContext);
    return <button className={theme}>点击</button>;
  }
  return <button>点击</button>;
}

useOptimistic Hook - Optimistic UI Updates

useOptimistic钩子 - 乐观UI更新

Manage optimistic UI updates for async operations:
typescript
import { useOptimistic } from 'react';

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

  const handleSubmit = async (formData) => {
    const newTodo = { id: Date.now(), text: formData.get('text') };

    // Optimistically add to UI
    addOptimisticTodo(newTodo);

    // Actually add to backend
    await addTodo(newTodo);
  };

  return (
    <form action={handleSubmit}>
      {optimisticTodos.map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <input type="text" name="text" />
      <button type="submit">Add Todo</button>
    </form>
  );
}
管理异步操作的乐观UI更新:
typescript
import { useOptimistic } from 'react';

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

  const handleSubmit = async (formData) => {
    const newTodo = { id: Date.now(), text: formData.get('text') };

    // 乐观地添加到UI
    addOptimisticTodo(newTodo);

    // 实际添加到后端
    await addTodo(newTodo);
  };

  return (
    <form action={handleSubmit}>
      {optimisticTodos.map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <input type="text" name="text" />
      <button type="submit">添加待办</button>
    </form>
  );
}

useFormStatus Hook - Form State

useFormStatus钩子 - 表单状态

Access form submission status from child components:
typescript
import { useFormStatus } from 'react';

function SubmitButton() {
  const { pending, data } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

function ContactForm() {
  return (
    <form action={submitForm}>
      <input name="email" type="email" />
      <SubmitButton />
    </form>
  );
}
从子组件访问表单提交状态:
typescript
import { useFormStatus } from 'react';

function SubmitButton() {
  const { pending, data } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '提交'}
    </button>
  );
}

function ContactForm() {
  return (
    <form action={submitForm}>
      <input name="email" type="email" />
      <SubmitButton />
    </form>
  );
}

useFormState Hook - Form State Management

useFormState钩子 - 表单状态管理

Manage form state with error handling:
typescript
import { useFormState } from 'react';

async function submitAction(prevState: string | null, formData: FormData) {
  const email = formData.get('email') as string;

  if (!email.includes('@')) {
    return 'Invalid email address';
  }

  await submitToDatabase(email);
  return null;
}

function EmailForm() {
  const [state, formAction] = useFormState(submitAction, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button type="submit">Subscribe</button>
      {state && <p className="error">{state}</p>}
    </form>
  );
}
带错误处理的表单状态管理:
typescript
import { useFormState } from 'react';

async function submitAction(prevState: string | null, formData: FormData) {
  const email = formData.get('email') as string;

  if (!email.includes('@')) {
    return '无效的邮箱地址';
  }

  await submitToDatabase(email);
  return null;
}

function EmailForm() {
  const [state, formAction] = useFormState(submitAction, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button type="submit">订阅</button>
      {state && <p className="error">{state}</p>}
    </form>
  );
}

Server Actions

Server Actions

Define server-side functions for form handling:
typescript
// app/actions.ts
'use server';

import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';

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

  // Validate input
  if (!title || !content) {
    return { error: 'Title and content are required' };
  }

  // Save to database
  const post = await db.post.create({
    data: { title, content }
  });

  // Update cache and redirect
  revalidatePath('/posts');
  redirect(`/posts/${post.id}`);
}
定义用于表单处理的服务器端函数:
typescript
// app/actions.ts
'use server';

import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';

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

  // 验证输入
  if (!title || !content) {
    return { error: '标题和内容为必填项' };
  }

  // 保存到数据库
  const post = await db.post.create({
    data: { title, content }
  });

  // 更新缓存并重定向
  revalidatePath('/posts');
  redirect(`/posts/${post.id}`);
}

Server Components

Server Components

Components that run exclusively on the server:
typescript
// app/posts/page.tsx - Server Component
async function PostsPage() {
  // Server-side data fetching
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  });

  return (
    <div>
      <h1>Latest Posts</h1>
      <PostsList posts={posts} />
    </div>
  );
}

// Client Component for interactivity
'use client';

function PostsList({ posts }: { posts: Post[] }) {
  const [selectedId, setSelectedId] = useState<number | null>(null);

  return (
    <ul>
      {posts.map(post => (
        <li
          key={post.id}
          onClick={() => setSelectedId(post.id)}
          className={selectedId === post.id ? 'selected' : ''}
        >
          {post.title}
        </li>
      ))}
    </ul>
  );
}
仅在服务器端运行的组件:
typescript
// app/posts/page.tsx - Server Component
async function PostsPage() {
  // 服务器端数据获取
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  });

  return (
    <div>
      <h1>最新文章</h1>
      <PostsList posts={posts} />
    </div>
  );
}

// 用于交互的Client Component
'use client';

function PostsList({ posts }: { posts: Post[] }) {
  const [selectedId, setSelectedId] = useState<number | null>(null);

  return (
    <ul>
      {posts.map(post => (
        <li
          key={post.id}
          onClick={() => setSelectedId(post.id)}
          className={selectedId === post.id ? 'selected' : ''}
        >
          {post.title}
        </li>
      ))}
    </ul>
  );
}

React Compiler

React Compiler

Automatic Optimization

自动优化

React Compiler automatically optimizes your components:
typescript
// Before React Compiler - manual memoization needed
const ExpensiveComponent = memo(function ExpensiveComponent({
  data,
  onUpdate
}) {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      computed: expensiveCalculation(item)
    }));
  }, [data]);

  const handleClick = useCallback((id) => {
    onUpdate(id);
  }, [onUpdate]);

  return (
    <div>
      {processedData.map(item => (
        <Item
          key={item.id}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
});

// After React Compiler - no manual optimization needed
function ExpensiveComponent({ data, onUpdate }) {
  const processedData = data.map(item => ({
    ...item,
    computed: expensiveCalculation(item)
  }));

  const handleClick = (id) => {
    onUpdate(id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item
          key={item.id}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
}
React Compiler会自动优化你的组件:
typescript
// React Compiler之前 - 需要手动记忆化
const ExpensiveComponent = memo(function ExpensiveComponent({
  data,
  onUpdate
}) {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      computed: expensiveCalculation(item)
    }));
  }, [data]);

  const handleClick = useCallback((id) => {
    onUpdate(id);
  }, [onUpdate]);

  return (
    <div>
      {processedData.map(item => (
        <Item
          key={item.id}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
});

// React Compiler之后 - 无需手动优化
function ExpensiveComponent({ data, onUpdate }) {
  const processedData = data.map(item => ({
    ...item,
    computed: expensiveCalculation(item)
  }));

  const handleClick = (id) => {
    onUpdate(id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item
          key={item.id}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
}

Installation and Setup

安装与配置

bash
undefined
bash
undefined

Install React Compiler

安装React Compiler

npm install -D babel-plugin-react-compiler@latest
npm install -D babel-plugin-react-compiler@latest

Install ESLint plugin for validation

安装用于验证的ESLint插件

npm install -D eslint-plugin-react-hooks@latest

```javascript
// babel.config.js
module.exports = {
  plugins: [
    'babel-plugin-react-compiler', // Must run first!
    // ... other plugins
  ],
};
javascript
// vite.config.js for Vite users
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});
npm install -D eslint-plugin-react-hooks@latest

```javascript
// babel.config.js
module.exports = {
  plugins: [
    'babel-plugin-react-compiler', // 必须放在最前面!
    // ... 其他插件
  ],
};
javascript
// Vite用户的vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});

Compiler Configuration

编译器配置

javascript
// babel.config.js with compiler options
module.exports = {
  plugins: [
    [
      'babel-plugin-react-compiler',
      {
        // Enable compilation for specific files
        target: '18', // or '19'
        // Debug mode for development
        debug: process.env.NODE_ENV === 'development'
      }
    ],
  ],
};

// Incremental adoption with overrides
module.exports = {
  plugins: [],
  overrides: [
    {
      test: './src/components/**/*.{js,jsx,ts,tsx}',
      plugins: ['babel-plugin-react-compiler']
    }
  ]
};
javascript
// 带编译器选项的babel.config.js
module.exports = {
  plugins: [
    [
      'babel-plugin-react-compiler',
      {
        // 为特定文件启用编译
        target: '18', // 或 '19'
        // 开发模式下启用调试
        debug: process.env.NODE_ENV === 'development'
      }
    ],
  ],
};

// 使用覆盖配置实现增量采用
module.exports = {
  plugins: [],
  overrides: [
    {
      test: './src/components/**/*.{js,jsx,ts,tsx}',
      plugins: ['babel-plugin-react-compiler']
    }
  ]
};

Advanced Server Components Patterns

高级Server Components模式

Mixed Server/Client Architecture

Server/Client混合架构

typescript
// Server Component for data fetching
async function ProductPage({ id }: { id: string }) {
  const product = await fetchProduct(id);
  const related = await fetchRelatedProducts(id);

  return (
    <div>
      <ProductDetails product={product} />
      <ProductGallery images={product.images} />
      <RelatedProducts products={related} />
    </div>
  );
}

// Client Component for interactivity
'use client';

function ProductDetails({ product }: { product: Product }) {
  const [quantity, setQuantity] = useState(1);
  const [isAdded, setIsAdded] = useState(false);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>

      <QuantitySelector
        value={quantity}
        onChange={setQuantity}
      />

      <AddToCartButton
        productId={product.id}
        quantity={quantity}
        onAdded={() => setIsAdded(true)}
      />

      {isAdded && <p>Added to cart!</p>}
    </div>
  );
}
typescript
// 用于数据获取的Server Component
async function ProductPage({ id }: { id: string }) {
  const product = await fetchProduct(id);
  const related = await fetchRelatedProducts(id);

  return (
    <div>
      <ProductDetails product={product} />
      <ProductGallery images={product.images} />
      <RelatedProducts products={related} />
    </div>
  );
}

// 用于交互的Client Component
'use client';

function ProductDetails({ product }: { product: Product }) {
  const [quantity, setQuantity] = useState(1);
  const [isAdded, setIsAdded] = useState(false);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>

      <QuantitySelector
        value={quantity}
        onChange={setQuantity}
      />

      <AddToCartButton
        productId={product.id}
        quantity={quantity}
        onAdded={() => setIsAdded(true)}
      />

      {isAdded && <p>已加入购物车!</p>}
    </div>
  );
}

Server Actions with Validation

带验证的Server Actions

typescript
'use server';

import { z } from 'zod';

const checkoutSchema = z.object({
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number().min(1)
  })),
  shippingAddress: z.object({
    street: z.string().min(1),
    city: z.string().min(1),
    zipCode: z.string().regex(/^\d{5}$/)
  }),
  paymentMethod: z.enum(['credit', 'paypal', 'apple'])
});

export async function processCheckout(
  prevState: any,
  formData: FormData
) {
  // Extract and validate data
  const rawData = {
    items: JSON.parse(formData.get('items') as string),
    shippingAddress: {
      street: formData.get('street'),
      city: formData.get('city'),
      zipCode: formData.get('zipCode')
    },
    paymentMethod: formData.get('paymentMethod')
  };

  const result = checkoutSchema.safeParse(rawData);

  if (!result.success) {
    return {
      error: 'Validation failed',
      fieldErrors: result.error.flatten().fieldErrors
    };
  }

  try {
    // Process payment
    const order = await createOrder(result.data);

    // Update inventory
    await updateInventory(result.data.items);

    // Send confirmation
    await sendConfirmationEmail(order);

    // Revalidate cache
    revalidatePath('/orders');

    return { success: true, orderId: order.id };
  } catch (error) {
    return { error: 'Payment failed' };
  }
}
typescript
'use server';

import { z } from 'zod';

const checkoutSchema = z.object({
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number().min(1)
  })),
  shippingAddress: z.object({
    street: z.string().min(1),
    city: z.string().min(1),
    zipCode: z.string().regex(/^\d{5}$/)
  }),
  paymentMethod: z.enum(['credit', 'paypal', 'apple'])
});

export async function processCheckout(
  prevState: any,
  formData: FormData
) {
  // 提取并验证数据
  const rawData = {
    items: JSON.parse(formData.get('items') as string),
    shippingAddress: {
      street: formData.get('street'),
      city: formData.get('city'),
      zipCode: formData.get('zipCode')
    },
    paymentMethod: formData.get('paymentMethod')
  };

  const result = checkoutSchema.safeParse(rawData);

  if (!result.success) {
    return {
      error: '验证失败',
      fieldErrors: result.error.flatten().fieldErrors
    };
  }

  try {
    // 处理支付
    const order = await createOrder(result.data);

    // 更新库存
    await updateInventory(result.data.items);

    // 发送确认邮件
    await sendConfirmationEmail(order);

    // 重新验证缓存
    revalidatePath('/orders');

    return { success: true, orderId: order.id };
  } catch (error) {
    return { error: '支付失败' };
  }
}

Concurrent Features

并发特性

useTransition for Non-Urgent Updates

useTransition处理非紧急更新

typescript
import { useTransition, useState } from 'react';

function SearchableList({ items }: { items: Item[] }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [filteredItems, setFilteredItems] = useState(items);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // Update input immediately
    setQuery(e.target.value);

    // Transition the filter operation
    startTransition(() => {
      setFilteredItems(
        items.filter(item =>
          item.name.toLowerCase().includes(e.target.value.toLowerCase())
        )
      );
    });
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="Search items..."
      />

      {isPending && <div className="loading">Filtering...</div>}

      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}
typescript
import { useTransition, useState } from 'react';

function SearchableList({ items }: { items: Item[] }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [filteredItems, setFilteredItems] = useState(items);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 立即更新输入框
    setQuery(e.target.value);

    // 以过渡方式执行过滤操作
    startTransition(() => {
      setFilteredItems(
        items.filter(item =>
          item.name.toLowerCase().includes(e.target.value.toLowerCase())
        )
      );
    });
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="搜索项目..."
      />

      {isPending && <div className="loading">过滤中...</div>}

      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

useDeferredValue for Expensive UI

useDeferredValue处理昂贵UI

typescript
import { useDeferredValue, useMemo } from 'react';

function DataGrid({ data }: { data: DataRow[] }) {
  const [searchTerm, setSearchTerm] = useState('');
  const deferredSearchTerm = useDeferredValue(searchTerm);

  const filteredData = useMemo(() => {
    return data.filter(row =>
      Object.values(row).some(value =>
        String(value).toLowerCase().includes(deferredSearchTerm.toLowerCase())
      )
    );
  }, [data, deferredSearchTerm]);

  return (
    <div>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
        placeholder="Search..."
        className={searchTerm !== deferredSearchTerm ? 'stale' : ''}
      />

      <DataGridRows
        data={filteredData}
        isStale={searchTerm !== deferredSearchTerm}
      />
    </div>
  );
}
typescript
import { useDeferredValue, useMemo } from 'react';

function DataGrid({ data }: { data: DataRow[] }) {
  const [searchTerm, setSearchTerm] = useState('');
  const deferredSearchTerm = useDeferredValue(searchTerm);

  const filteredData = useMemo(() => {
    return data.filter(row =>
      Object.values(row).some(value =>
        String(value).toLowerCase().includes(deferredSearchTerm.toLowerCase())
      )
    );
  }, [data, deferredSearchTerm]);

  return (
    <div>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
        placeholder="搜索..."
        className={searchTerm !== deferredSearchTerm ? 'stale' : ''}
      />

      <DataGridRows
        data={filteredData}
        isStale={searchTerm !== deferredSearchTerm}
      />
    </div>
  );
}

Testing React 19 Features

测试React 19特性

Testing Server Actions

测试Server Actions

typescript
import { render, screen, fireEvent } from '@testing-library/react';
import { jest } from '@jest/globals';
import ContactForm from './ContactForm';

// Mock server action
const mockSubmitForm = jest.fn();

describe('ContactForm', () => {
  it('submits form with server action', async () => {
    render(<ContactForm />);

    fireEvent.change(screen.getByLabelText('Email'), {
      target: { value: 'test@example.com' }
    });

    fireEvent.click(screen.getByText('Submit'));

    expect(mockSubmitForm).toHaveBeenCalledWith(
      expect.any(FormData)
    );
  });

  it('shows loading state during submission', async () => {
    mockSubmitForm.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));

    render(<ContactForm />);

    fireEvent.click(screen.getByText('Submit'));

    expect(screen.getByText('Submitting...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('Submit')).toBeInTheDocument();
    });
  });
});
typescript
import { render, screen, fireEvent } from '@testing-library/react';
import { jest } from '@jest/globals';
import ContactForm from './ContactForm';

// 模拟server action
const mockSubmitForm = jest.fn();

describe('ContactForm', () => {
  it('使用server action提交表单', async () => {
    render(<ContactForm />);

    fireEvent.change(screen.getByLabelText('邮箱'), {
      target: { value: 'test@example.com' }
    });

    fireEvent.click(screen.getByText('提交'));

    expect(mockSubmitForm).toHaveBeenCalledWith(
      expect.any(FormData)
    );
  });

  it('提交过程中显示加载状态', async () => {
    mockSubmitForm.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));

    render(<ContactForm />);

    fireEvent.click(screen.getByText('提交'));

    expect(screen.getByText('提交中...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('提交')).toBeInTheDocument();
    });
  });
});

Testing Optimistic Updates

测试乐观更新

typescript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { jest } from '@jest/globals';
import TodoList from './TodoList';

describe('useOptimistic', () => {
  it('shows optimistic update immediately', async () => {
    const mockAddTodo = jest.fn(() => new Promise(resolve => setTimeout(resolve, 100)));

    render(
      <TodoList
        todos={[]}
        addTodo={mockAddTodo}
      />
    );

    fireEvent.change(screen.getByPlaceholderText('Add a todo'), {
      target: { value: 'New todo' }
    });

    fireEvent.click(screen.getByText('Add'));

    // Optimistic update appears immediately
    expect(screen.getByText('New todo')).toBeInTheDocument();

    // Wait for actual submission
    await waitFor(() => {
      expect(mockAddTodo).toHaveBeenCalledWith({
        id: expect.any(Number),
        text: 'New todo'
      });
    });
  });
});
typescript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { jest } from '@jest/globals';
import TodoList from './TodoList';

describe('useOptimistic', () => {
  it('立即显示乐观更新', async () => {
    const mockAddTodo = jest.fn(() => new Promise(resolve => setTimeout(resolve, 100)));

    render(
      <TodoList
        todos={[]}
        addTodo={mockAddTodo}
      />
    );

    fireEvent.change(screen.getByPlaceholderText('添加待办'), {
      target: { value: '新待办' }
    });

    fireEvent.click(screen.getByText('添加'));

    // 乐观更新立即显示
    expect(screen.getByText('新待办')).toBeInTheDocument();

    // 等待实际提交完成
    await waitFor(() => {
      expect(mockAddTodo).toHaveBeenCalledWith({
        id: expect.any(Number),
        text: '新待办'
      });
    });
  });
});

Performance Best Practices

性能最佳实践

React Compiler Guidelines

React Compiler指南

  1. Write Standard React Code: The compiler works best with idiomatic React patterns
  2. Avoid Manual Memoization: Let the compiler handle useMemo, useCallback, and memo
  3. Keep Components Pure: Avoid side effects in render
  4. Use Stable References: Pass stable objects as props
typescript
// Good: Clean, idiomatic React
function ProductCard({ product, onAddToCart }) {
  const [quantity, setQuantity] = useState(1);

  const handleAdd = () => {
    onAddToCart(product.id, quantity);
  };

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <input
        type="number"
        value={quantity}
        onChange={e => setQuantity(Number(e.target.value))}
        min="1"
      />
      <button onClick={handleAdd}>Add to Cart</button>
    </div>
  );
}

// Avoid: Manual optimization
function ProductCard({ product, onAddToCart }) {
  const [quantity, setQuantity] = useState(1);

  const handleAdd = useCallback(() => {
    onAddToCart(product.id, quantity);
  }, [product.id, quantity, onAddToCart]);

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <QuantityInput
        value={quantity}
        onChange={setQuantity}
      />
      <button onClick={handleAdd}>Add to Cart</button>
    </div>
  );
}
  1. 编写标准React代码:编译器最适合惯用的React模式
  2. 避免手动记忆化:让编译器处理useMemo、useCallback和memo
  3. 保持组件纯净:避免在渲染中产生副作用
  4. 使用稳定引用:传递稳定对象作为props
typescript
// 良好:简洁、惯用的React代码
function ProductCard({ product, onAddToCart }) {
  const [quantity, setQuantity] = useState(1);

  const handleAdd = () => {
    onAddToCart(product.id, quantity);
  };

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <input
        type="number"
        value={quantity}
        onChange={e => setQuantity(Number(e.target.value))}
        min="1"
      />
      <button onClick={handleAdd}>加入购物车</button>
    </div>
  );
}

// 避免:手动优化
function ProductCard({ product, onAddToCart }) {
  const [quantity, setQuantity] = useState(1);

  const handleAdd = useCallback(() => {
    onAddToCart(product.id, quantity);
  }, [product.id, quantity, onAddToCart]);

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <QuantityInput
        value={quantity}
        onChange={setQuantity}
      />
      <button onClick={handleAdd}>加入购物车</button>
    </div>
  );
}

Server Components Best Practices

Server Components最佳实践

  1. Keep Server Components Server-Only: No event handlers, hooks, or browser APIs
  2. Minimize Client Components: Only use 'use client' when necessary
  3. Pass Data as Props: Serialize data when passing from Server to Client
  4. Use Server Actions for Mutations: Keep data operations on the server
typescript
// Good: Server Component for static content
async function ProductPage({ id }: { id: string }) {
  const product = await fetchProduct(id);

  return (
    <article>
      <header>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
      </header>

      <img
        src={product.imageUrl}
        alt={product.name}
        width={600}
        height={400}
      />

      <PriceDisplay price={product.price} />
      <AddToCartForm productId={product.id} />
    </article>
  );
}

// Client Component only for interactivity
'use client';

function AddToCartForm({ productId }: { productId: string }) {
  const [isAdding, setIsAdding] = useState(false);

  async function handleSubmit() {
    setIsAdding(true);
    await addToCart(productId);
    setIsAdding(false);
  }

  return (
    <form action={handleSubmit}>
      <button type="submit" disabled={isAdding}>
        {isAdding ? 'Adding...' : 'Add to Cart'}
      </button>
    </form>
  );
}
  1. 保持Server Components纯服务器端:不包含事件处理器、钩子或浏览器API
  2. 最小化Client Components:仅在必要时使用'use client'
  3. 通过Props传递数据:从Server到Client传递数据时进行序列化
  4. 使用Server Actions处理突变:将数据操作保留在服务器端
typescript
// 良好:用于静态内容的Server Component
async function ProductPage({ id }: { id: string }) {
  const product = await fetchProduct(id);

  return (
    <article>
      <header>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
      </header>

      <img
        src={product.imageUrl}
        alt={product.name}
        width={600}
        height={400}
      />

      <PriceDisplay price={product.price} />
      <AddToCartForm productId={product.id} />
    </article>
  );
}

// 仅用于交互的Client Component
'use client';

function AddToCartForm({ productId }: { productId: string }) {
  const [isAdding, setIsAdding] = useState(false);

  async function handleSubmit() {
    setIsAdding(true);
    await addToCart(productId);
    setIsAdding(false);
  }

  return (
    <form action={handleSubmit}>
      <button type="submit" disabled={isAdding}>
        {isAdding ? '添加中...' : '加入购物车'}
      </button>
    </form>
  );
}

Migration Guide

迁移指南

From React 18 to 19

从React 18到19

  1. Update Dependencies:
bash
npm install react@19 react-dom@19
  1. Adopt Server Components:
    • Identify data-fetching components
    • Remove client-side code from Server Components
    • Add 'use client' directive where needed
  2. Replace Manual Optimistic Updates:
typescript
// Before
function TodoList({ todos, addTodo }) {
  const [optimisticTodos, setOptimisticTodos] = useState(todos);

  const handleAdd = async (text) => {
    const newTodo = { id: Date.now(), text };
    setOptimisticTodos([...optimisticTodos, newTodo]);
    await addTodo(newTodo);
  };
}

// After
function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => [...state, newTodo]
  );

  const handleAdd = async (formData) => {
    const newTodo = { id: Date.now(), text: formData.get('text') };
    addOptimisticTodo(newTodo);
    await addTodo(newTodo);
  };
}
  1. Enable React Compiler:
    • Install babel-plugin-react-compiler
    • Remove manual memoization
    • Let the compiler optimize automatically
  1. 更新依赖
bash
npm install react@19 react-dom@19
  1. 采用Server Components
    • 识别数据获取类组件
    • 从Server Components中移除客户端代码
    • 在必要时添加'use client'指令
  2. 替换手动乐观更新
typescript
// 之前
function TodoList({ todos, addTodo }) {
  const [optimisticTodos, setOptimisticTodos] = useState(todos);

  const handleAdd = async (text) => {
    const newTodo = { id: Date.now(), text };
    setOptimisticTodos([...optimisticTodos, newTodo]);
    await addTodo(newTodo);
  };
}

// 之后
function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => [...state, newTodo]
  );

  const handleAdd = async (formData) => {
    const newTodo = { id: Date.now(), text: formData.get('text') };
    addOptimisticTodo(newTodo);
    await addTodo(newTodo);
  };
}
  1. 启用React Compiler
    • 安装babel-plugin-react-compiler
    • 移除手动记忆化代码
    • 让编译器自动进行优化

References

参考资料