react-fundamentals-19
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuick Reference
快速参考
| Feature | Pattern | Example |
|---|---|---|
| Server Component | Default (no directive) | |
| Client Component | | |
| Server Action | | |
| use() hook | Read promises/context | |
| Concept | Syntax |
|---|---|
| Conditional render | |
| List render | |
| Props destructure | |
| State update | |
| 特性 | 模式 | 示例 |
|---|---|---|
| 服务器组件 | 默认(无指令) | |
| 客户端组件 | | |
| Server Action | | |
| use() 钩子 | 读取Promise/上下文 | |
| 概念 | 语法 |
|---|---|
| 条件渲染 | |
| 列表渲染 | |
| Props 解构 | |
| 状态更新 | |
When to Use This Skill
何时使用本技能
Use for React 19 core concepts:
- Learning React 19 new features and syntax
- Deciding between Server and Client Components
- Setting up Server Actions for forms
- Understanding the use() hook for async data
- Component composition and JSX patterns
- Error handling with Suspense and Error Boundaries
For specific topics: hooks → , state management → , performance →
react-hooks-completereact-state-managementreact-performance用于React 19核心概念相关场景:
- 学习React 19新特性与语法
- 选择服务器组件与客户端组件的适用场景
- 为表单配置Server Actions
- 理解use()钩子处理异步数据的方式
- 组件组合与JSX模式
- 使用Suspense与错误边界处理错误
针对特定主题:钩子相关→,状态管理→,性能优化→
react-hooks-completereact-state-managementreact-performanceReact 19 Fundamentals
React 19 基础
Overview
概述
React 19 (released December 2024) introduces significant improvements including the React Compiler, Server Components as first-class citizens, new hooks, and enhanced form handling.
React 19(于2024年12月发布)带来了重大改进,包括React编译器、作为一等公民的服务器组件、新钩子以及增强的表单处理能力。
JSX and Components
JSX与组件
Function Components
函数组件
tsx
// Basic component
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
// With children
function Card({ children, title }: { children: React.ReactNode; title: string }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// Arrow function component
const Button: React.FC<{ onClick: () => void; children: React.ReactNode }> = ({
onClick,
children,
}) => (
<button onClick={onClick}>{children}</button>
);tsx
// 基础组件
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
// 包含子组件
function Card({ children, title }: { children: React.ReactNode; title: string }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// 箭头函数组件
const Button: React.FC<{ onClick: () => void; children: React.ReactNode }> = ({
onClick,
children,
}) => (
<button onClick={onClick}>{children}</button>
);JSX Expressions
JSX表达式
tsx
function ProductCard({ product }: { product: Product }) {
return (
<div className="product">
{/* Conditional rendering */}
{product.onSale && <span className="badge">Sale!</span>}
{/* Ternary */}
{product.inStock ? (
<button>Add to Cart</button>
) : (
<span>Out of Stock</span>
)}
{/* Logical AND (short-circuit) */}
{product.reviews.length > 0 && (
<Reviews reviews={product.reviews} />
)}
{/* Mapping arrays */}
<ul>
{product.features.map((feature, index) => (
<li key={feature.id || index}>{feature.name}</li>
))}
</ul>
{/* Nullish coalescing */}
<span>{product.discount ?? 0}% off</span>
</div>
);
}tsx
function ProductCard({ product }: { product: Product }) {
return (
<div className="product">
{/* 条件渲染 */}
{product.onSale && <span className="badge">Sale!</span>}
{/* 三元表达式 */}
{product.inStock ? (
<button>Add to Cart</button>
) : (
<span>Out of Stock</span>
)}
{/* 逻辑与(短路求值) */}
{product.reviews.length > 0 && (
<Reviews reviews={product.reviews} />
)}
{/* 数组映射 */}
<ul>
{product.features.map((feature, index) => (
<li key={feature.id || index}>{feature.name}</li>
))}
</ul>
/* 空值合并运算符 */
<span>{product.discount ?? 0}% off</span>
</div>
);
}Server vs Client Components
服务器组件 vs 客户端组件
Server Components (Default)
服务器组件(默认)
tsx
// app/posts/page.tsx - Server Component by default
import { db } from '@/lib/db';
async function PostsPage() {
// Direct database access - runs on server only
const posts = await db.posts.findMany({
orderBy: { createdAt: 'desc' },
});
return (
<div>
<h1>Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
export default PostsPage;tsx
// app/posts/page.tsx - 默认是服务器组件
import { db } from '@/lib/db';
async function PostsPage() {
// 直接访问数据库 - 仅在服务器端运行
const posts = await db.posts.findMany({
orderBy: { createdAt: 'desc' },
});
return (
<div>
<h1>Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
export default PostsPage;Client Components
客户端组件
tsx
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
</div>
);
}tsx
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
</div>
);
}Composing Server and Client Components
组合服务器组件与客户端组件
tsx
// Server Component
import { db } from '@/lib/db';
import { LikeButton } from './LikeButton'; // Client Component
async function Post({ id }: { id: string }) {
const post = await db.posts.findUnique({ where: { id } });
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* Client component for interactivity */}
<LikeButton postId={id} initialLikes={post.likes} />
</article>
);
}tsx
// LikeButton.tsx - Client Component
'use client';
import { useState, useTransition } from 'react';
import { likePost } from './actions';
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [isPending, startTransition] = useTransition();
const handleLike = () => {
startTransition(async () => {
const newLikes = await likePost(postId);
setLikes(newLikes);
});
};
return (
<button onClick={handleLike} disabled={isPending}>
{isPending ? 'Liking...' : `Like (${likes})`}
</button>
);
}tsx
// 服务器组件
import { db } from '@/lib/db';
import { LikeButton } from './LikeButton'; // 客户端组件
async function Post({ id }: { id: string }) {
const post = await db.posts.findUnique({ where: { id } });
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* 客户端组件用于交互功能 */}
<LikeButton postId={id} initialLikes={post.likes} />
</article>
);
}tsx
// LikeButton.tsx - 客户端组件
'use client';
import { useState, useTransition } from 'react';
import { likePost } from './actions';
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [isPending, startTransition] = useTransition();
const handleLike = () => {
startTransition(async () => {
const newLikes = await likePost(postId);
setLikes(newLikes);
});
};
return (
<button onClick={handleLike} disabled={isPending}>
{isPending ? 'Liking...' : `Like (${likes})`}
</button>
);
}Server Actions
Server Actions
Defining Server Actions
定义Server Actions
tsx
// actions.ts
'use server';
import { db } from '@/lib/db';
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;
await db.posts.create({
data: { title, content },
});
revalidatePath('/posts');
}
export async function likePost(postId: string) {
const post = await db.posts.update({
where: { id: postId },
data: { likes: { increment: 1 } },
});
return post.likes;
}tsx
// actions.ts
'use server';
import { db } from '@/lib/db';
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;
await db.posts.create({
data: { title, content },
});
revalidatePath('/posts');
}
export async function likePost(postId: string) {
const post = await db.posts.update({
where: { id: postId },
data: { likes: { increment: 1 } },
});
return post.likes;
}Using Server Actions in Forms
在表单中使用Server Actions
tsx
// Form with Server Action
import { createPost } from './actions';
function CreatePostForm() {
return (
<form action={createPost}>
<input type="text" name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit">Create Post</button>
</form>
);
}tsx
// 集成Server Action的表单
import { createPost } from './actions';
function CreatePostForm() {
return (
<form action={createPost}>
<input type="text" name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit">Create Post</button>
</form>
);
}Using useActionState
使用useActionState
tsx
'use client';
import { useActionState } from 'react';
import { createPost } from './actions';
function CreatePostForm() {
const [state, formAction, isPending] = useActionState(createPost, {
error: null,
success: false,
});
return (
<form action={formAction}>
<input type="text" name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">Post created!</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}tsx
'use client';
import { useActionState } from 'react';
import { createPost } from './actions';
function CreatePostForm() {
const [state, formAction, isPending] = useActionState(createPost, {
error: null,
success: false,
});
return (
<form action={formAction}>
<input type="text" name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">Post created!</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}The use() Hook
use()钩子
Reading Promises
读取Promise
tsx
import { use, Suspense } from 'react';
// Fetch function that returns a promise
async function fetchComments(postId: string) {
const res = await fetch(`/api/posts/${postId}/comments`);
return res.json();
}
// Component that uses the promise
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise);
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
// Parent component
function Post({ postId }: { postId: string }) {
const commentsPromise = fetchComments(postId);
return (
<article>
<h1>Post Title</h1>
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</article>
);
}tsx
import { use, Suspense } from 'react';
// 返回Promise的获取函数
async function fetchComments(postId: string) {
const res = await fetch(`/api/posts/${postId}/comments`);
return res.json();
}
// 使用Promise的组件
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise);
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
// 父组件
function Post({ postId }: { postId: string }) {
const commentsPromise = fetchComments(postId);
return (
<article>
<h1>Post Title</h1>
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</article>
);
}Reading Context Conditionally
条件读取上下文
tsx
import { use } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton({ showTheme }: { showTheme: boolean }) {
// use() can be called conditionally (unlike useContext)
if (showTheme) {
const theme = use(ThemeContext);
return <button style={{ background: theme.primary }}>Themed</button>;
}
return <button>Default</button>;
}tsx
import { use } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton({ showTheme }: { showTheme: boolean }) {
// use()可以被条件调用(不同于useContext)
if (showTheme) {
const theme = use(ThemeContext);
return <button style={{ background: theme.primary }}>Themed</button>;
}
return <button>Default</button>;
}Props and State
Props与状态
Props Patterns
Props模式
tsx
// Destructuring props
function UserCard({ user, onEdit, className = '' }: UserCardProps) {
return (
<div className={`user-card ${className}`}>
<h3>{user.name}</h3>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
// Spreading props
function Button({ children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
return <button {...props}>{children}</button>;
}
// Default props with TypeScript
interface CardProps {
title: string;
elevated?: boolean;
padding?: 'sm' | 'md' | 'lg';
}
function Card({ title, elevated = false, padding = 'md' }: CardProps) {
return (
<div
className={`card ${elevated ? 'elevated' : ''} padding-${padding}`}
>
<h2>{title}</h2>
</div>
);
}tsx
// 解构Props
function UserCard({ user, onEdit, className = '' }: UserCardProps) {
return (
<div className={`user-card ${className}`}>
<h3>{user.name}</h3>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
// 展开Props
function Button({ children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
return <button {...props}>{children}</button>;
}
// TypeScript默认Props
interface CardProps {
title: string;
elevated?: boolean;
padding?: 'sm' | 'md' | 'lg';
}
function Card({ title, elevated = false, padding = 'md' }: CardProps) {
return (
<div
className={`card ${elevated ? 'elevated' : ''} padding-${padding}`}
>
<h2>{title}</h2>
</div>
);
}State Updates
状态更新
tsx
'use client';
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
// Functional update for derived state
const addTodo = (text: string) => {
setTodos((prev) => [
...prev,
{ id: Date.now(), text, completed: false },
]);
};
// Toggle item
const toggleTodo = (id: number) => {
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// Remove item
const removeTodo = (id: number) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}tsx
'use client';
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
// 派生状态的函数式更新
const addTodo = (text: string) => {
setTodos((prev) => [
...prev,
{ id: Date.now(), text, completed: false },
]);
};
// 切换项状态
const toggleTodo = (id: number) => {
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// 删除项
const removeTodo = (id: number) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}Suspense and Error Boundaries
Suspense与错误边界
Suspense for Loading States
Suspense处理加载状态
tsx
import { Suspense } from 'react';
function App() {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</div>
);
}tsx
import { Suspense } from 'react';
function App() {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</div>
);
}Error Boundaries
错误边界
tsx
'use client';
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary fallback={<ErrorPage />}>
<MainContent />
</ErrorBoundary>
);
}tsx
'use client';
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// 使用示例
function App() {
return (
<ErrorBoundary fallback={<ErrorPage />}>
<MainContent />
</ErrorBoundary>
);
}React 19 Error Boundary Hook Pattern
React 19错误边界钩子模式
tsx
'use client';
import { useErrorBoundary } from 'react-error-boundary';
function DataLoader() {
const { showBoundary } = useErrorBoundary();
const loadData = async () => {
try {
const data = await fetchData();
return data;
} catch (error) {
showBoundary(error);
}
};
// ...
}tsx
'use client';
import { useErrorBoundary } from 'react-error-boundary';
function DataLoader() {
const { showBoundary } = useErrorBoundary();
const loadData = async () => {
try {
const data = await fetchData();
return data;
} catch (error) {
showBoundary(error);
}
};
// ...
}Fragments and Portals
片段与传送门
Fragments
片段
tsx
// Short syntax
function List() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
);
}
// With key (for mapping)
function DefinitionList({ items }: { items: { term: string; definition: string }[] }) {
return (
<dl>
{items.map((item) => (
<Fragment key={item.term}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}
</dl>
);
}tsx
// 短语法
function List() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
);
}
// 带key的片段(用于映射)
function DefinitionList({ items }: { items: { term: string; definition: string }[] }) {
return (
<dl>
{items.map((item) => (
<Fragment key={item.term}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}
</dl>
);
}Portals
传送门
tsx
'use client';
import { createPortal } from 'react-dom';
import { useEffect, useState } from 'react';
function Modal({ children, isOpen }: { children: ReactNode; isOpen: boolean }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted || !isOpen) return null;
return createPortal(
<div className="modal-overlay">
<div className="modal-content">{children}</div>
</div>,
document.body
);
}tsx
'use client';
import { createPortal } from 'react-dom';
import { useEffect, useState } from 'react';
function Modal({ children, isOpen }: { children: ReactNode; isOpen: boolean }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted || !isOpen) return null;
return createPortal(
<div className="modal-overlay">
<div className="modal-content">{children}</div>
</div>,
document.body
);
}Best Practices
最佳实践
1. Component Organization
1. 组件组织
text
src/
components/
ui/ # Reusable UI components
Button.tsx
Card.tsx
features/ # Feature-specific components
auth/
LoginForm.tsx
SignupForm.tsx
layout/ # Layout components
Header.tsx
Footer.tsx
hooks/ # Custom hooks
useAuth.ts
useLocalStorage.ts
lib/ # Utilities and helpers
utils.ts
api.ts
types/ # TypeScript types
index.tstext
src/
components/
ui/ # 可复用UI组件
Button.tsx
Card.tsx
features/ # 特性专属组件
auth/
LoginForm.tsx
SignupForm.tsx
layout/ # 布局组件
Header.tsx
Footer.tsx
hooks/ # 自定义钩子
useAuth.ts
useLocalStorage.ts
lib/ # 工具与辅助函数
utils.ts
api.ts
types/ # TypeScript类型定义
index.ts2. Naming Conventions
2. 命名规范
- Components: PascalCase ()
UserProfile.tsx - Hooks: camelCase with prefix (
use)useAuth.ts - Utilities: camelCase ()
formatDate.ts - Types: PascalCase (,
User)ApiResponse
- 组件:大驼峰命名()
UserProfile.tsx - 钩子:小驼峰命名并以为前缀(
use)useAuth.ts - 工具函数:小驼峰命名()
formatDate.ts - 类型:大驼峰命名(,
User)ApiResponse
3. Key Prop Usage
3. Key属性使用
tsx
// Good - stable unique identifier
{items.map((item) => (
<Item key={item.id} data={item} />
))}
// Avoid - index as key (unless list is static)
{items.map((item, index) => (
<Item key={index} data={item} /> // Can cause issues with reordering
))}tsx
// 推荐 - 稳定的唯一标识符
{items.map((item) => (
<Item key={item.id} data={item} />
))}
// 避免 - 使用索引作为key(除非列表是静态的)
{items.map((item, index) => (
<Item key={index} data={item} /> // 列表重排时可能引发问题
))}4. Avoid Inline Object/Function Props
4. 避免内联对象/函数Props
tsx
// Bad - creates new object/function every render
<Component style={{ color: 'red' }} onClick={() => handleClick(id)} />
// Good - stable references
const style = useMemo(() => ({ color: 'red' }), []);
const handleClickMemo = useCallback(() => handleClick(id), [id]);
<Component style={style} onClick={handleClickMemo} />tsx
// 不推荐 - 每次渲染都会创建新的对象/函数
<Component style={{ color: 'red' }} onClick={() => handleClick(id)} />
// 推荐 - 使用稳定的引用
const style = useMemo(() => ({ color: 'red' }), []);
const handleClickMemo = useCallback(() => handleClick(id), [id]);
<Component style={style} onClick={handleClickMemo} />