React 19新特性解析

React 19新特性解析

概述与背景

React 19是React团队在2024年发布的重大版本更新,带来了诸多令人期待的新特性。这些更新不仅简化了开发流程,还大幅提升了应用性能和用户体验。

graph TB
    subgraph React 19核心架構
        A[React 19] --> B[Actions<br/>表單處理]
        A --> C[新Hooks<br/>useFormStatus等]
        A --> D[服務端組件<br/>RSC]
        A --> E[Ref簡化<br/>直接prop]
        
        B --> F[統一數據流]
        C --> G[狀態管理簡化]
        D --> H[性能優化]
        E --> I[開發體驗提升]
    end

    style A fill:#61dafb
    style B fill:#c8e6c9
    style C fill:#c8e6c9
    style D fill:#c8e6c9
    style E fill:#c8e6c9
graph LR
    subgraph React版本對比
        A[React 18] --> A1[onSubmit+狀態管理]
        A --> A2[useEffect+fetch]
        A --> A3[手動管理加載狀態]
        A --> A4[forwardRef]
        
        B[React 19] --> B1[Actions]
        B --> B2[use Hook]
        B --> B3[useFormStatus]
        B --> B4[直接prop]
    end

    style A fill:#ffcdd2
    style B fill:#c8e6c9

React 19的核心目标

  1. 简化数据获取:统一的服务端/客户端数据流
  2. 优化表单处理:Actions机制取代传统onSubmit
  3. 提升用户体验:内置乐观更新和加载状态
  4. 增强性能:服务端组件和流式渲染
  5. 改善开发者体验:简化API,减少样板代码

版本对比

特性React 18React 19
表单处理onSubmit + 状态管理Actions
数据获取useEffect + fetchuse Hook
加载状态手动管理useFormStatus
乐观更新自行实现useOptimistic
Ref传递forwardRef直接prop
元数据Helmet等库原生支持

核心新特性详解

graph TD
    subgraph Actions工作流程
        A[用戶提交表單] --> B[Action函數]
        B --> C{執行結果}
        C -->|成功| D[更新UI]
        C -->|失敗| E[顯示錯誤]
        C -->|加載中| F[顯示加載狀態]
        
        G[useFormStatus] --> F
        H[useOptimistic] --> I[樂觀更新]
        J[useActionState] --> K[狀態管理]
    end

    style A fill:#fff9c4
    style B fill:#e1f5fe
    style D fill:#c8e6c9
    style E fill:#ffcdd2
sequenceDiagram
    participant U as 用戶
    participant F as 表單組件
    participant A as Action
    participant S as 服務器

    Note over U,S: React 19 Actions流程
    U->>F: 提交表單
    F->>A: 調用Action函數
    A->>S: 發送數據
    A->>A: useOptimistic樂觀更新UI
    S-->>A: 返回結果
    alt 成功
        A->>F: 更新最終狀態
        F-->>U: 顯示成功
    else 失敗
        A->>F: 回滾樂觀更新
        F-->>U: 顯示錯誤
    end

1. Actions:简化表单处理

React 19引入了Actions概念,彻底改变了表单处理的方式。

传统方式的痛点

// React 18及之前:需要大量样板代码
function LoginForm() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [success, setSuccess] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setError(null);
    
    try {
      const formData = new FormData(e.target);
      const response = await submitLogin(formData);
      
      if (response.ok) {
        setSuccess(true);
        // 重定向、更新状态等
      } else {
        setError(response.message);
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      
      <button type="submit" disabled={isLoading}>
        {isLoading ? '登录中...' : '登录'}
      </button>
      
      {error && <div className="error">{error}</div>}
      {success && <div className="success">登录成功!</div>}
    </form>
  );
}

痛点分析

  • 需要管理多个状态(loading、error、success)
  • 需要preventDefault阻止默认行为
  • 需要手动构建FormData
  • 错误处理逻辑分散
  • 测试困难

React 19 Actions方式

// React 19:简洁优雅
function LoginForm() {
  // 服务端Action
  async function loginAction(formData) {
    'use server'; // Next.js服务端标记
    
    const result = await submitLogin(formData);
    
    if (!result.ok) {
      throw new Error(result.message);
    }
    
    redirect('/dashboard');
  }

  return (
    <form action={loginAction}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      
      <SubmitButton>登录</SubmitButton>
      <ErrorBoundary />
    </form>
  );
}

// 提交按钮组件
function SubmitButton({ children }) {
  const { pending } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? '登录中...' : children}
    </button>
  );
}

优势

  • 无需手动管理loading状态
  • 自动处理FormData
  • 内置错误处理机制
  • 代码量减少60%+
  • 更容易测试

Actions的工作原理

用户提交表单

1. 拦截提交事件(自动)

2. 收集表单数据为FormData

3. 调用Action函数

4. 自动管理pending状态

5. 处理返回结果或错误

6. 更新UI或重定向

客户端Actions

Actions不仅限于服务端,也可以在纯客户端使用:

// 客户端Action
function ContactForm() {
  const [state, formAction] = useActionState(async (prevState, formData) => {
    const name = formData.get('name');
    const email = formData.get('email');
    const message = formData.get('message');

    // 客户端验证
    if (!name || !email || !message) {
      return { error: '请填写所有字段' };
    }

    try {
      await fetch('/api/contact', {
        method: 'POST',
        body: formData
      });
      
      return { success: true, message: '发送成功!' };
    } catch (error) {
      return { error: '发送失败,请重试' };
    }
  }, null);

  return (
    <form action={formAction}>
      <input name="name" />
      <input name="email" type="email" />
      <textarea name="message" />
      
      <button type="submit">发送</button>
      
      {state?.error && <div className="error">{state.error}</div>}
      {state?.success && <div className="success">{state.message}</div>}
    </form>
  );
}

2. useFormStatus Hook

配合Actions使用,轻松获取表单状态。

基础用法

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();
  
  // pending: boolean - 是否正在提交
  // data: FormData | null - 表单数据
  // method: 'get' | 'post' - 表单方法
  // action: Function | null - Action函数
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? (
        <>
          <Spinner />
          提交中...
        </>
      ) : (
        '提交'
      )}
    </button>
  );
}

重要限制

useFormStatus必须从表单内部组件调用:

// ❌ 错误:在同一个组件中使用
function Form() {
  const { pending } = useFormStatus(); // 总是返回false!
  
  return (
    <form action={submitForm}>
      <input name="title" />
      <button disabled={pending}>提交</button> {/* 不工作 */}
    </form>
  );
}

// ✅ 正确:从子组件中使用
function Form() {
  return (
    <form action={submitForm}>
      <input name="title" />
      <SubmitButton /> {/* 分离为子组件 */}
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus(); // 正确获取状态
  return <button disabled={pending}>{pending ? '提交中' : '提交'}</button>;
}

实战示例:智能提交按钮

function SmartSubmitButton({ loadingText = '提交中...', children }) {
  const { pending, data } = useFormStatus();
  
  // 可以访问表单数据
  const hasFiles = data?.has('attachments');
  
  return (
    <button 
      type="submit" 
      disabled={pending}
      className={pending ? 'opacity-50 cursor-not-allowed' : ''}
    >
      {pending ? (
        <span className="flex items-center gap-2">
          <LoadingSpinner size="sm" />
          {loadingText}
          {hasFiles && <span className="text-sm">(上传中...)</span>}
        </span>
      ) : children}
    </button>
  );
}

// 使用
function UploadForm() {
  return (
    <form action={uploadFiles}>
      <input type="file" name="attachments" multiple />
      <SmartSubmitButton loadingText="上传文件中...">
        上传文件
      </SmartSubmitButton>
    </form>
  );
}

3. useActionState Hook

管理Action的执行状态和结果,替代手动状态管理。

API签名

function useActionState<State, Payload>(
  action: (state: State, payload: Payload) => State | Promise<State>,
  initialState: State,
  permalink?: string
): [state: State, dispatch: (payload: Payload) => void, isPending: boolean];

基础示例

import { useActionState } from 'react';

function TodoForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      const title = formData.get('title');
      
      // 验证
      if (!title?.trim()) {
        return { error: '标题不能为空', todos: prevState?.todos || [] };
      }
      
      // API调用
      try {
        const newTodo = await addTodo({ title });
        return { 
          success: '添加成功!',
          todos: [...(prevState?.todos || []), newTodo]
        };
      } catch (error) {
        return { 
          error: error.message,
          todos: prevState?.todos || []
        };
      }
    },
    { todos: [], error: null, success: null }
  );

  return (
    <div>
      <form action={formAction}>
        <input 
          name="title" 
          placeholder="输入待办事项"
          disabled={isPending}
        />
        <button type="submit" disabled={isPending}>
          {isPending ? '添加中...' : '添加'}
        </button>
      </form>
      
      {/* 显示反馈 */}
      {state.error && (
        <div className="error-message">{state.error}</div>
      )}
      {state.success && (
        <div className="success-message">{state.success}</div>
      )}
      
      {/* 待办列表 */}
      <ul>
        {state.todos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}

与useReducer对比

// useReducer方式(React 18)
function TodoForm() {
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case 'SUBMIT':
          return { ...state, isLoading: true };
        case 'SUCCESS':
          return { 
            isLoading: false, 
            todos: [...state.todos, action.payload] 
          };
        case 'ERROR':
          return { isLoading: false, error: action.error };
        default:
          return state;
      }
    },
    { todos: [], isLoading: false, error: null }
  );

  const handleSubmit = async (e) => {
    e.preventDefault();
    dispatch({ type: 'SUBMIT' });
    
    try {
      const formData = new FormData(e.target);
      const todo = await addTodo(formData);
      dispatch({ type: 'SUCCESS', payload: todo });
    } catch (error) {
      dispatch({ type: 'ERROR', error: error.message });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* ... */}
    </form>
  );
}

// useActionState方式(React 19)
function TodoForm() {
  const [state, formAction, isPending] = useActionState(
    async (prev, formData) => {
      const todo = await addTodo(formData);
      return { ...prev, todos: [...prev.todos, todo] };
    },
    { todos: [] }
  );

  return (
    <form action={formAction}>
      {/* ... */}
    </form>
  );
}

4. useOptimistic:乐观更新

实现流畅的乐观更新体验,无需等待服务器响应。

什么是乐观更新?

乐观更新(Optimistic Update)是一种UI模式:先立即更新UI,给用户即时反馈,然后在后台发送请求。如果请求失败,再回滚更改。

用户体验对比

模式用户操作UI响应服务器响应用户感知
悲观更新点击提交等待…1-3秒后更新卡顿、等待
乐观更新点击提交立即更新1-3秒后确认流畅、即时

基础用法

import { useOptimistic } from 'react';

function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => {
      // 乐观更新:立即添加到列表
      return [...state, { ...newTodo, pending: true }];
    }
  );

  async function handleSubmit(formData) {
    const title = formData.get('title');
    const tempId = Date.now();
    
    // 1. 乐观更新
    addOptimisticTodo({ id: tempId, title, pending: true });
    
    // 2. 实际请求
    try {
      const newTodo = await addTodo({ title });
      // 服务器返回真实数据后,React会用真实数据替换临时数据
    } catch (error) {
      // 失败时自动回滚
      console.error('添加失败:', error);
    }
  }

  return (
    <div>
      <form action={handleSubmit}>
        <input name="title" placeholder="输入待办事项" />
        <button type="submit">添加</button>
      </form>
      
      <ul>
        {optimisticTodos.map(todo => (
          <li 
            key={todo.id}
            className={todo.pending ? 'opacity-50 italic' : ''}
          >
            {todo.title}
            {todo.pending && <span className="text-sm"> (保存中...)</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

完整的点赞功能示例

function LikeButton({ postId, initialLikes, isLiked }) {
  const [likes, setLikes] = useState(initialLikes);
  const [liked, setLiked] = useState(isLiked);
  
  const [optimisticLikes, toggleLike] = useOptimistic(
    { count: likes, liked },
    (state, newValue) => ({
      count: state.count + (newValue ? 1 : -1),
      liked: newValue
    })
  );

  async function handleLike() {
    // 立即更新UI
    toggleLike(!optimisticLikes.liked);
    
    // 发送请求
    try {
      const result = await fetch(`/api/posts/${postId}/like`, {
        method: optimisticLikes.liked ? 'DELETE' : 'POST'
      });
      const data = await result.json();
      
      // 更新真实数据
      setLikes(data.likes);
      setLiked(data.liked);
    } catch (error) {
      // 失败时自动回滚
      toast.error('操作失败,请重试');
    }
  }

  return (
    <button 
      onClick={handleLike}
      className={optimisticLikes.liked ? 'liked' : ''}
    >
      {optimisticLikes.liked ? '❤️' : '🤍'} {optimisticLikes.count}
    </button>
  );
}

评论列表示例

function CommentList({ postId, initialComments }) {
  const [comments, setComments] = useState(initialComments);
  
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [...state, { ...newComment, sending: true }]
  );

  const submitComment = async (formData) => {
    const content = formData.get('comment');
    const tempId = `temp-${Date.now()}`;
    
    // 乐观添加
    addOptimisticComment({
      id: tempId,
      content,
      author: '我',
      createdAt: new Date().toISOString(),
      sending: true
    });
    
    // 发送请求
    const response = await fetch(`/api/posts/${postId}/comments`, {
      method: 'POST',
      body: formData
    });
    const newComment = await response.json();
    
    // 替换临时评论为真实评论
    setComments(prev => 
      prev.map(c => c.id === tempId ? newComment : c)
    );
  };

  return (
    <div>
      <ul>
        {optimisticComments.map(comment => (
          <li key={comment.id} className={comment.sending ? 'opacity-50' : ''}>
            <strong>{comment.author}:</strong> {comment.content}
            {comment.sending && <span> 发送中...</span>}
          </li>
        ))}
      </ul>
      
      <form action={submitComment}>
        <textarea name="comment" required />
        <button type="submit">发送评论</button>
      </form>
    </div>
  );
}

5. use API

统一的数据获取和Context读取方式。

API签名

function use<T>(resource: Promise<T> | Context<T>): T;

用途1:Promise数据获取

import { use } from 'react';

// 传统方式(需要Suspense边界)
function UserProfile({ userId }) {
  const userPromise = fetch(`/api/users/${userId}`).then(r => r.json());
  
  return (
    <Suspense fallback={<Spinner />}>
      <UserDetails userPromise={userPromise} />
    </Suspense>
  );
}

function UserDetails({ userPromise }) {
  const user = use(userPromise); // 自动处理Promise
  
  return (
    <div>
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

用途2:Context读取

import { use } from 'react';

// 传统方式
function ThemeComponent() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <div className={theme}>
      <button onClick={toggleTheme}>切换主题</button>
    </div>
  );
}

// use API方式
function ThemeComponent() {
  const { theme, toggleTheme } = use(ThemeContext);
  
  return (
    <div className={theme}>
      <button onClick={toggleTheme}>切换主题</button>
    </div>
  );
}

条件使用(突破Hook规则限制)

传统Hooks不能在条件语句中调用,但use可以:

function CommentSection({ showComments, commentsPromise }) {
  // ✅ 条件中使用use
  if (showComments) {
    const comments = use(commentsPromise);
    return <CommentList comments={comments} />;
  }
  
  return <button onClick={() => setShowComments(true)}>加载评论</button>;
}

完整的数据获取模式

function ProductPage({ productId }) {
  // 在服务端或客户端获取数据
  const productPromise = getProduct(productId);
  const reviewsPromise = getReviews(productId);
  
  return (
    <div>
      <Suspense fallback={<ProductSkeleton />}>
        <ProductDetails productPromise={productPromise} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews reviewsPromise={reviewsPromise} />
      </Suspense>
    </div>
  );
}

function ProductDetails({ productPromise }) {
  const product = use(productPromise);
  
  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <span>${product.price}</span>
    </article>
  );
}

function Reviews({ reviewsPromise }) {
  const reviews = use(reviewsPromise);
  
  return (
    <section>
      <h2>用户评价</h2>
      {reviews.map(review => (
        <ReviewCard key={review.id} review={review} />
      ))}
    </section>
  );
}

6. 服务端组件(Server Components)增强

React 19进一步完善了服务端组件支持。

服务端 vs 客户端组件

// ============================================
// Server Component(默认)
// - 在服务端渲染
// - 可以直接访问数据库、文件系统
// - 不能使用客户端交互(useState、onClick等)
// - 减少发送到客户端的JS

async function ProductList() {
  // 直接访问数据库
  const products = await db.products.findMany();
  
  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          <ProductCard product={product} />
        </li>
      ))}
    </ul>
  );
}

// ============================================
// Client Component(需显式声明)
// - 在客户端渲染和hydration
// - 可以使用所有React特性
// - 不能直接访问服务端资源

'use client';

import { useState } from 'react';

function ProductCard({ product }) {
  const [isLiked, setIsLiked] = useState(false);
  
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <button onClick={() => setIsLiked(!isLiked)}>
        {isLiked ? '❤️' : '🤍'}
      </button>
    </div>
  );
}

混合使用模式

// Server Component
async function ProductPage({ productId }) {
  // 服务端获取数据
  const product = await db.products.find(productId);
  const recommendedProducts = await getRecommendations(productId);
  
  return (
    <div>
      {/* 服务端组件:静态内容 */}
      <ProductHeader product={product} />
      
      {/* 客户端组件:交互功能 */}
      <AddToCartButton productId={product.id} />
      
      {/* 服务端组件:推荐列表 */}
      <ProductRecommendations products={recommendedProducts} />
      
      {/* 客户端组件:评论系统 */}
      <ReviewSection productId={product.id} />
    </div>
  );
}

// 服务端组件
function ProductHeader({ product }) {
  return (
    <header>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <span className="price">${product.price}</span>
    </header>
  );
}

// 客户端组件
'use client';

function AddToCartButton({ productId }) {
  const [quantity, setQuantity] = useState(1);
  
  const handleAdd = async () => {
    await addToCart(productId, quantity);
    toast.success('已添加到购物车');
  };
  
  return (
    <div>
      <input 
        type="number" 
        value={quantity}
        onChange={(e) => setQuantity(Number(e.target.value))}
      />
      <button onClick={handleAdd}>加入购物车</button>
    </div>
  );
}

服务端Action

// app/actions/product.ts
'use server';

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

export async function createProduct(formData: FormData) {
  const name = formData.get('name') as string;
  const price = Number(formData.get('price'));
  const description = formData.get('description') as string;
  
  // 验证
  if (!name || price <= 0) {
    return { error: '请填写有效信息' };
  }
  
  // 数据库操作
  await db.products.create({
    data: { name, price, description }
  });
  
  // 重新验证缓存
  revalidatePath('/products');
  
  // 重定向
  redirect('/products');
}

export async function deleteProduct(id: string) {
  await db.products.delete({ where: { id } });
  revalidatePath('/products');
}

// app/admin/products/page.tsx
import { createProduct, deleteProduct } from './actions';

export default function AdminProducts() {
  return (
    <div>
      <h1>产品管理</h1>
      
      <form action={createProduct}>
        <input name="name" placeholder="产品名称" required />
        <input name="price" type="number" step="0.01" required />
        <textarea name="description" />
        <button type="submit">创建产品</button>
      </form>
      
      <ProductList />
    </div>
  );
}

async function ProductList() {
  const products = await db.products.findMany();
  
  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {product.name} - ${product.price}
          <form action={deleteProduct.bind(null, product.id)}>
            <button type="submit">删除</button>
          </form>
        </li>
      ))}
    </ul>
  );
}

7. 简化的Ref行为

ref作为prop传递,不再需要forwardRef。

对比示例

// ============================================
// React 18及之前:必须使用forwardRef

import { forwardRef } from 'react';

const MyInput = forwardRef((props, ref) => (
  <input ref={ref} {...props} className="custom-input" />
));

MyInput.displayName = 'MyInput';

// 使用
function Form() {
  const inputRef = useRef(null);
  
  return (
    <form>
      <MyInput ref={inputRef} placeholder="输入内容" />
      <button onClick={() => inputRef.current?.focus()}>
        聚焦输入框
      </button>
    </form>
  );
}

// ============================================
// React 19:直接接收ref作为prop

function MyInput({ ref, ...props }) {
  return <input ref={ref} {...props} className="custom-input" />;
}

// 使用方式完全相同
function Form() {
  const inputRef = useRef(null);
  
  return (
    <form>
      <MyInput ref={inputRef} placeholder="输入内容" />
      <button onClick={() => inputRef.current?.focus()}>
        聚焦输入框
      </button>
    </form>
  );
}

复合组件的Ref转发

// React 19复合组件示例
function FormField({ ref, label, error, ...props }) {
  const id = useId();
  
  return (
    <div className="form-field">
      <label htmlFor={id}>{label}</label>
      <input 
        ref={ref}
        id={id}
        aria-invalid={!!error}
        aria-describedby={error ? `${id}-error` : undefined}
        {...props}
      />
      {error && (
        <span id={`${id}-error`} className="error">
          {error}
        </span>
      )}
    </div>
  );
}

// 使用
function ContactForm() {
  const emailRef = useRef(null);
  
  return (
    <form>
      <FormField
        ref={emailRef}
        label="邮箱"
        type="email"
        name="email"
        placeholder="your@email.com"
      />
    </form>
  );
}

8. 文档元数据支持

直接在组件中设置meta标签,无需Helmet等库。

基础用法

function BlogPost({ post }) {
  return (
    <>
      {/* 页面标题 */}
      <title>{post.title} | 我的博客</title>
      
      {/* 基础meta标签 */}
      <meta name="description" content={post.excerpt} />
      <meta name="keywords" content={post.tags.join(', ')} />
      <meta name="author" content={post.author} />
      
      {/* Open Graph */}
      <meta property="og:title" content={post.title} />
      <meta property="og:description" content={post.excerpt} />
      <meta property="og:image" content={post.coverImage} />
      <meta property="og:type" content="article" />
      <meta property="og:url" content={`https://example.com/posts/${post.slug}`} />
      
      {/* Twitter Card */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={post.title} />
      <meta name="twitter:description" content={post.excerpt} />
      <meta name="twitter:image" content={post.coverImage} />
      
      {/* 文章内容 */}
      <article>
        <h1>{post.title}</h1>
        <p>{post.content}</p>
      </article>
    </>
  );
}

动态SEO组件

function SEOHead({ 
  title, 
  description, 
  image = '/og-default.jpg',
  url,
  type = 'website'
}) {
  const fullTitle = `${title} | 我的网站`;
  
  return (
    <>
      <title>{fullTitle}</title>
      <meta name="description" content={description} />
      
      <meta property="og:title" content={fullTitle} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={image} />
      <meta property="og:type" content={type} />
      <meta property="og:url" content={url} />
      
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={fullTitle} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={image} />
      
      {/* 结构化数据 */}
      <script type="application/ld+json">
        {JSON.stringify({
          "@context": "https://schema.org",
          "@type": type === 'article' ? 'Article' : 'WebPage',
          "headline": title,
          "description": description,
          "image": image,
          "url": url
        })}
      </script>
    </>
  );
}

// 使用
function ProductPage({ product }) {
  return (
    <>
      <SEOHead
        title={product.name}
        description={product.description}
        image={product.images[0]}
        url={`https://example.com/products/${product.id}`}
        type="product"
      />
      {/* 产品内容 */}
    </>
  );
}

9. 样式表支持

支持样式表优先级管理,避免CSS冲突。

基础用法

function Component() {
  return (
    <>
      {/* 基础样式 */}
      <link 
        rel="stylesheet" 
        href="/styles/base.css" 
        precedence="low" 
      />
      
      {/* 组件样式 */}
      <link 
        rel="stylesheet" 
        href="/styles/component.css" 
        precedence="medium" 
      />
      
      {/* 关键样式 */}
      <link 
        rel="stylesheet" 
        href="/styles/critical.css" 
        precedence="high" 
      />
      
      <div className="styled-content">
        内容
      </div>
    </>
  );
}

优先级说明

优先级加载顺序适用场景
low最后加载主题覆盖、第三方库
medium中间加载组件样式
high最先加载重置样式、关键CSS
function App() {
  return (
    <>
      {/* 即使组件顺序不同,样式也会按优先级排序 */}
      <link rel="stylesheet" href="/theme.css" precedence="low" />
      <link rel="stylesheet" href="/critical.css" precedence="high" />
      <link rel="stylesheet" href="/components.css" precedence="medium" />
      
      {/* 实际加载顺序:
        1. critical.css (high)
        2. components.css (medium)
        3. theme.css (low)
      */}
    </>
  );
}

10. 资源预加载

新的API用于优化资源加载。

API列表

import { 
  preload,      // 预加载资源
  preinit,      // 预初始化脚本
  preconnect    // 预连接域名
} from 'react-dom';

使用示例

function ProductPage({ productId }) {
  // 预连接到API服务器
  preconnect('https://api.example.com');
  
  // 预加载字体
  preload('/fonts/inter-var.woff2', {
    as: 'font',
    type: 'font/woff2',
    crossOrigin: 'anonymous'
  });
  
  // 预加载图片
  preload(`/products/${productId}/hero.jpg`, {
    as: 'image'
  });
  
  // 预初始化关键脚本
  preinit('/scripts/analytics.js', {
    as: 'script'
  });
  
  return (
    <>
      <ProductHero productId={productId} />
      <ProductDetails productId={productId} />
    </>
  );
}

预加载策略

// 按需预加载
function ProductCard({ product }) {
  const [isHovered, setIsHovered] = useState(false);
  
  // 鼠标悬停时预加载详情页资源
  useEffect(() => {
    if (isHovered) {
      preload(`/products/${product.id}/details.js`, {
        as: 'script'
      });
      preload(`/products/${product.id}/hero.jpg`, {
        as: 'image'
      });
    }
  }, [isHovered, product.id]);
  
  return (
    <Link 
      href={`/products/${product.id}`}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <img src={product.thumbnail} alt={product.name} />
      <h3>{product.name}</h3>
    </Link>
  );
}

// 路由级别预加载
function App() {
  const pathname = usePathname();
  
  useEffect(() => {
    // 根据当前页面预加载可能访问的下一页
    if (pathname === '/') {
      preload('/pages/home.js', { as: 'script' });
    } else if (pathname.startsWith('/products')) {
      preload('/pages/cart.js', { as: 'script' });
    }
  }, [pathname]);
  
  return <Router />;
}

实战案例:完整的全栈表单应用

项目结构

app/
├── actions/
│   ├── todo.ts          # Todo服务端Action
│   └── auth.ts          # 认证Action
├── components/
│   ├── TodoForm.tsx     # Todo表单组件
│   ├── TodoList.tsx     # Todo列表组件
│   └── SubmitButton.tsx # 智能提交按钮
├── lib/
│   └── db.ts            # 数据库连接
└── page.tsx             # 主页面

服务端Action

// app/actions/todo.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { db } from '../lib/db';
import { auth } from '../lib/auth';

// 创建Todo
export async function createTodo(formData: FormData) {
  const session = await auth();
  if (!session?.user) {
    return { error: '请先登录' };
  }

  const title = formData.get('title') as string;
  
  if (!title?.trim()) {
    return { error: '标题不能为空' };
  }

  await db.todo.create({
    data: {
      title: title.trim(),
      userId: session.user.id
    }
  });

  revalidatePath('/todos');
  return { success: true };
}

// 切换完成状态
export async function toggleTodo(id: string) {
  const todo = await db.todo.findUnique({ where: { id } });
  
  if (!todo) {
    return { error: 'Todo不存在' };
  }

  await db.todo.update({
    where: { id },
    data: { completed: !todo.completed }
  });

  revalidatePath('/todos');
  return { success: true };
}

// 删除Todo
export async function deleteTodo(id: string) {
  await db.todo.delete({ where: { id } });
  revalidatePath('/todos');
}

// 更新Todo
export async function updateTodo(id: string, formData: FormData) {
  const title = formData.get('title') as string;
  
  if (!title?.trim()) {
    return { error: '标题不能为空' };
  }

  await db.todo.update({
    where: { id },
    data: { title: title.trim() }
  });

  revalidatePath('/todos');
  return { success: true };
}

客户端组件

// app/components/SubmitButton.tsx
'use client';

import { useFormStatus } from 'react-dom';

interface SubmitButtonProps {
  children: React.ReactNode;
  loadingText?: string;
  className?: string;
}

export function SubmitButton({ 
  children, 
  loadingText = '处理中...', 
  className

💬 評論區

返回文章列表