React 19 新特性实战
Next.js 16 基于 React 19 构建,带来了一系列新 Hook 和编译器优化。
use()替代 useEffect 读取 Promise 和 Context、useActionState管理表单状态、useOptimistic实现乐观更新、React Compiler 自动 memo。本章逐个讲清它们在 AI SaaS 中的实战用法。
1. use() Hook
1.1 读取 Promise
use() 可以在组件渲染期间读取 Promise 的值——配合 Suspense 使用:
'use client'
import { use, Suspense } from 'react'
function ChatTitle({ titlePromise }: { titlePromise: Promise<string> }) {
const title = use(titlePromise) // 等待 Promise 解析
return <h1>{title}</h1>
}
export function ChatHeader({ chatId }: { chatId: string }) {
const titlePromise = fetchChatTitle(chatId) // 不 await,传递 Promise
return (
<Suspense fallback={<Skeleton className="h-8 w-48" />}>
<ChatTitle titlePromise={titlePromise} />
</Suspense>
)
}与 await 的区别:
await(Server Component 中使用):
├── 阻塞渲染直到数据到达
└── 只能在 async 函数/组件中使用
use()(Client Component 中使用):
├── 配合 Suspense 实现 loading 状态
├── 不需要 async 组件
└── 可以在条件语句中使用(await 不行)
1.2 条件读取
'use client'
import { use } from 'react'
function UserProfile({ userPromise, isLoggedIn }: {
userPromise: Promise<User>
isLoggedIn: boolean
}) {
if (!isLoggedIn) {
return <LoginButton />
}
// use() 可以在条件语句中调用(useEffect/useState 不行!)
const user = use(userPromise)
return <div>欢迎, {user.name}</div>
}1.3 读取 Context
use() 也可以替代 useContext()——区别是可以在条件语句中调用:
'use client'
import { use, createContext } from 'react'
const ThemeContext = createContext<'light' | 'dark'>('light')
function ThemedButton({ showTheme }: { showTheme: boolean }) {
if (!showTheme) return <button>普通按钮</button>
const theme = use(ThemeContext) // 条件调用!useContext 做不到
return (
<button className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>
主题按钮
</button>
)
}2. useActionState
2.1 基础用法
useActionState 是 React 19 统一的表单 Action 状态管理:
'use client'
import { useActionState } from 'react'
import { createChat } from '@/app/chat/actions'
export function CreateChatForm() {
const [state, formAction, isPending] = useActionState(createChat, {
errors: {},
message: '',
})
return (
<form action={formAction}>
<input name="title" placeholder="对话标题" />
{state.errors?.title && (
<p className="text-red-500 text-sm">{state.errors.title[0]}</p>
)}
<button type="submit" disabled={isPending}>
{isPending ? '创建中...' : '创建对话'}
</button>
{state.message && <p className="text-red-500">{state.message}</p>}
</form>
)
}2.2 与 Server Actions 配合
// app/chat/actions.ts
'use server'
type State = { errors?: Record<string, string[]>; message?: string }
export async function createChat(prevState: State, formData: FormData): Promise<State> {
const title = formData.get('title') as string
if (!title || title.length < 1) {
return { errors: { title: ['标题不能为空'] } }
}
try {
await db.insert(chats).values({ title })
revalidatePath('/chat')
} catch {
return { message: '创建失败,请重试' }
}
redirect('/chat')
}2.3 useFormStatus
在表单内部的子组件中获取提交状态:
'use client'
import { useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending} className="w-full">
{pending ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
提交中...
</span>
) : (
'提交'
)}
</button>
)
}
// 使用(必须是 form 的子组件)
<form action={formAction}>
<input name="title" />
<SubmitButton /> {/* 自动感知表单提交状态 */}
</form>3. useOptimistic
3.1 基础模式
'use client'
import { useOptimistic } from 'react'
type Message = { id: string; content: string; status: 'sending' | 'sent' | 'error' }
export function ChatMessages({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage: Message) => [...state, newMessage],
)
async function sendMessage(formData: FormData) {
const content = formData.get('content') as string
const tempId = crypto.randomUUID()
// 1. 立即在 UI 中显示(乐观)
addOptimisticMessage({
id: tempId,
content,
status: 'sending',
})
// 2. 实际发送到服务端
await sendMessageAction(content)
}
return (
<div>
{optimisticMessages.map((msg) => (
<div key={msg.id} className={msg.status === 'sending' ? 'opacity-60' : ''}>
{msg.content}
{msg.status === 'sending' && <span className="text-xs ml-2">发送中...</span>}
</div>
))}
<form action={sendMessage}>
<input name="content" placeholder="输入消息" />
<button type="submit">发送</button>
</form>
</div>
)
}3.2 切换状态
export function FavoriteButton({ chatId, isFavorite }: { chatId: string; isFavorite: boolean }) {
const [optimisticFavorite, toggleOptimistic] = useOptimistic(
isFavorite,
(current) => !current,
)
async function handleToggle() {
toggleOptimistic(null) // 立即切换
await toggleFavoriteAction(chatId) // 然后同步到服务端
}
return (
<form action={handleToggle}>
<button type="submit">
{optimisticFavorite ? '★ 已收藏' : '☆ 收藏'}
</button>
</form>
)
}4. React Compiler
4.1 是什么
React Compiler(原 React Forget)是一个编译时优化工具——它自动为你的组件添加 useMemo、useCallback、React.memo:
// 你写的代码(无需手动 memo)
function ChatList({ chats, onSelect }) {
const sorted = chats.sort((a, b) => b.updatedAt - a.updatedAt)
return (
<ul>
{sorted.map(chat => (
<li key={chat.id} onClick={() => onSelect(chat.id)}>
{chat.title}
</li>
))}
</ul>
)
}
// React Compiler 编译后(自动优化)
function ChatList({ chats, onSelect }) {
const sorted = useMemo(() =>
chats.sort((a, b) => b.updatedAt - a.updatedAt),
[chats]
)
const handleSelect = useCallback((id) => onSelect(id), [onSelect])
// ... 自动 memo 化
}4.2 启用
// next.config.ts
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
},
}pnpm add -D babel-plugin-react-compiler4.3 影响
启用 React Compiler 后:
├── 移除所有手动 useMemo/useCallback(编译器自动添加)
├── 移除 React.memo 包装(编译器自动判断)
├── 代码更简洁(不需要依赖数组)
├── 减少人为错误(依赖数组写错)
└── 性能自动优化(不需要思考什么时候 memo)
4.4 注意事项
// React Compiler 要求:组件必须遵循 React 规则
// ✅ 纯组件(编译器可以优化)
function ChatItem({ chat }) {
return <div>{chat.title}</div>
}
// ❌ 副作用在渲染中(编译器无法优化)
function ChatItem({ chat }) {
console.log('rendering', chat.id) // 渲染时的副作用
globalCounter++ // 修改外部变量
return <div>{chat.title}</div>
}5. 其他 React 19 改进
5.1 ref 作为 prop
不再需要 forwardRef:
// React 18:需要 forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />
})
// React 19:ref 直接作为 prop
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />
}5.2 Document Metadata
在组件中直接使用 <title>、<meta>、<link>——React 19 自动提升到 <head>:
function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.summary} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}5.3 Stylesheet 优先级
function Component() {
return (
<>
<link rel="stylesheet" href="/base.css" precedence="default" />
<link rel="stylesheet" href="/theme.css" precedence="high" />
{/* React 19 确保按优先级顺序加载 */}
</>
)
}6. 新特性速查表
| 特性 | 用途 | 替代 |
|---|---|---|
use(Promise) | 在 Client Component 中读取异步数据 | useEffect + useState |
use(Context) | 条件读取 Context | useContext(不能条件调用) |
useActionState | Server Action 表单状态管理 | useState + 手动处理 |
useFormStatus | 子组件感知表单提交状态 | 手动 props 传递 |
useOptimistic | 乐观更新 UI | 手动 state 管理 |
| React Compiler | 自动 memo 优化 | 手动 useMemo/useCallback |
| ref as prop | ref 直接作为 prop | forwardRef |
本章小结
use():读取 Promise(配合 Suspense)或 Context(支持条件调用)useActionState:Server Actions 的表单状态管理,返回 state + formAction + isPendinguseFormStatus:子组件自动感知表单提交状态,无需 props 传递useOptimistic:乐观更新,Server Action 完成前立即反映 UI 变化- React Compiler:编译时自动添加 memo 优化,代码更简洁
至此,第四篇「组件系统」全部完成。下一篇将深入状态管理。