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)是一个编译时优化工具——它自动为你的组件添加 useMemouseCallbackReact.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-compiler

4.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

在组件中直接使用 &lt;title&gt;&lt;meta&gt;&lt;link&gt;——React 19 自动提升到 &lt;head&gt;

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)条件读取 ContextuseContext(不能条件调用)
useActionStateServer Action 表单状态管理useState + 手动处理
useFormStatus子组件感知表单提交状态手动 props 传递
useOptimistic乐观更新 UI手动 state 管理
React Compiler自动 memo 优化手动 useMemo/useCallback
ref as propref 直接作为 propforwardRef

本章小结

  • use():读取 Promise(配合 Suspense)或 Context(支持条件调用)
  • useActionState:Server Actions 的表单状态管理,返回 state + formAction + isPending
  • useFormStatus:子组件自动感知表单提交状态,无需 props 传递
  • useOptimistic:乐观更新,Server Action 完成前立即反映 UI 变化
  • React Compiler:编译时自动添加 memo 优化,代码更简洁

至此,第四篇「组件系统」全部完成。下一篇将深入状态管理。