react-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact 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最佳实践
- Dependency Arrays: Always specify correct dependencies in useEffect
- State Structure: Keep state minimal and avoid redundant state
- Component Size: Keep components small and focused
- Custom Hooks: Extract complex logic into reusable custom hooks
- TypeScript: Use TypeScript for type safety
- Keys: Use stable IDs as keys for list items, not array indices
- Immutability: Never mutate state directly
- Effects: Use effects only for synchronization with external systems
- Performance: Profile before optimizing with useMemo/useCallback
- 依赖数组:始终在useEffect中指定正确的依赖
- 状态结构:保持状态最小化,避免冗余状态
- 组件大小:保持组件小巧且聚焦单一职责
- 自定义钩子:将复杂逻辑提取为可复用的自定义钩子
- TypeScript:使用TypeScript保证类型安全
- Keys:使用稳定ID作为列表项的key,而非数组索引
- 不可变性:永远不要直接修改状态
- 副作用:仅在与外部系统同步时使用副作用
- 性能:先分析性能瓶颈,再使用useMemo/useCallback进行优化
React 19 Specific Best Practices
React 19特定最佳实践
- Server Components: Use Server Components for data fetching and static content
- Client Components: Mark components as 'use client' only when necessary
- Actions: Use Server Actions for mutations and form submissions
- Optimistic Updates: Implement useOptimistic for better UX
- use() Hook: Use for reading promises and context conditionally
- Form State: Use useFormState and useFormStatus for complex forms
- Concurrent Features: Leverage useTransition for non-urgent updates
- Error Boundaries: Implement proper error handling with error boundaries
- Server Components:使用Server Components处理数据获取和静态内容
- Client Components:仅在必要时将组件标记为'use client'
- Actions:使用Server Actions处理突变和表单提交
- 乐观更新:使用useOptimistic实现更好的用户体验
- use()钩子:用于条件性读取Promise和Context
- 表单状态:使用useFormState和useFormStatus处理复杂表单
- 并发特性:利用useTransition处理非紧急更新
- 错误边界:使用错误边界实现完善的错误处理
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 hook reads the value from a resource like a Promise or Context:
use()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()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
undefinedbash
undefinedInstall 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指南
- Write Standard React Code: The compiler works best with idiomatic React patterns
- Avoid Manual Memoization: Let the compiler handle useMemo, useCallback, and memo
- Keep Components Pure: Avoid side effects in render
- 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>
);
}- 编写标准React代码:编译器最适合惯用的React模式
- 避免手动记忆化:让编译器处理useMemo、useCallback和memo
- 保持组件纯净:避免在渲染中产生副作用
- 使用稳定引用:传递稳定对象作为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最佳实践
- Keep Server Components Server-Only: No event handlers, hooks, or browser APIs
- Minimize Client Components: Only use 'use client' when necessary
- Pass Data as Props: Serialize data when passing from Server to Client
- 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>
);
}- 保持Server Components纯服务器端:不包含事件处理器、钩子或浏览器API
- 最小化Client Components:仅在必要时使用'use client'
- 通过Props传递数据:从Server到Client传递数据时进行序列化
- 使用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
- Update Dependencies:
bash
npm install react@19 react-dom@19-
Adopt Server Components:
- Identify data-fetching components
- Remove client-side code from Server Components
- Add 'use client' directive where needed
-
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);
};
}- Enable React Compiler:
- Install babel-plugin-react-compiler
- Remove manual memoization
- Let the compiler optimize automatically
- 更新依赖:
bash
npm install react@19 react-dom@19-
采用Server Components:
- 识别数据获取类组件
- 从Server Components中移除客户端代码
- 在必要时添加'use client'指令
-
替换手动乐观更新:
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);
};
}- 启用React Compiler:
- 安装babel-plugin-react-compiler
- 移除手动记忆化代码
- 让编译器自动进行优化
References
参考资料
- React 19 Official Docs: https://react.dev/blog/2024/04/25/react-19
- React Server Components: https://react.dev/reference/rsc/server-components
- React Compiler: https://react.dev/learn/react-compiler
- TypeScript with React: https://react-typescript-cheatsheet.netlify.app/
- React 19官方文档: https://react.dev/blog/2024/04/25/react-19
- React Server Components: https://react.dev/reference/rsc/server-components
- React Compiler: https://react.dev/learn/react-compiler
- TypeScript与React: https://react-typescript-cheatsheet.netlify.app/