“UI里就可以请求数据库”,让你陌生的 React 特性

目录

  1. [React Actions 与 useActionState](#React Actions 与 useActionState "#react-actions")
  2. [现代 Suspense 组件](#现代 Suspense 组件 "#modern-suspense")
  3. [React 服务器组件(RSC)](#React 服务器组件(RSC) "#server-components")
  4. [服务器函数(Server Functions)](#服务器函数(Server Functions) "#server-functions")
  5. 最佳实践与使用场景

React Actions 与 useActionState {#react-actions}

useActionState Hook

useActionState 是 React 19 引入的新 Hook,用于处理表单提交和异步操作的状态管理。

基础语法

javascript 复制代码
const [state, formAction, isPending] = useActionState(actionFunction, initialState);

基础示例

javascript 复制代码
'use client';

import { useActionState } from 'react';

async function updateName(previousState, formData) {
  const name = formData.get('name');
  
  // 模拟异步操作
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  if (name.length < 2) {
    return { error: '姓名至少需要2个字符' };
  }
  
  return { success: `Hello ${name}!` };
}

export default function NameForm() {
  const [state, formAction, isPending] = useActionState(updateName, null);
  
  return (
    <form action={formAction}>
      <input type="text" name="name" placeholder="输入您的姓名" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '提交'}
      </button>
      
      {state?.error && <p style={{color: 'red'}}>{state.error}</p>}
      {state?.success && <p style={{color: 'green'}}>{state.success}</p>}
    </form>
  );
}

服务器端 Actions

服务器端 Actions 允许客户端直接调用服务器函数,无需手动设置 API 端点。

定义服务器 Action

javascript 复制代码
// app/actions.js
'use server';

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

export async function createPost(prevState, formData) {
  const title = formData.get('title');
  const content = formData.get('content');
  
  // 验证数据
  if (!title || !content) {
    return {
      error: '标题和内容都是必需的'
    };
  }
  
  try {
    // 保存到数据库
    const post = await db.post.create({
      data: { title, content }
    });
    
    // 重新验证相关页面
    revalidatePath('/posts');
    
    // 重定向到新文章
    redirect(`/posts/${post.id}`);
    
  } catch (error) {
    return {
      error: '创建文章失败'
    };
  }
}

export async function deletePost(postId) {
  try {
    await db.post.delete({
      where: { id: postId }
    });
    
    revalidatePath('/posts');
    return { success: true };
  } catch (error) {
    return { error: '删除失败' };
  }
}

客户端使用服务器 Action

javascript 复制代码
'use client';

import { useActionState } from 'react';
import { createPost } from './actions';

export default function PostForm() {
  const [state, formAction, isPending] = useActionState(createPost, null);
  
  return (
    <form action={formAction}>
      <div>
        <label htmlFor="title">标题:</label>
        <input 
          type="text" 
          id="title" 
          name="title" 
          required 
        />
      </div>
      
      <div>
        <label htmlFor="content">内容:</label>
        <textarea 
          id="content" 
          name="content" 
          rows={5} 
          required 
        />
      </div>
      
      <button type="submit" disabled={isPending}>
        {isPending ? '发布中...' : '发布文章'}
      </button>
      
      {state?.error && (
        <div style={{color: 'red', marginTop: '10px'}}>
          {state.error}
        </div>
      )}
    </form>
  );
}

渐进式增强

Actions 支持渐进式增强,即使 JavaScript 被禁用也能正常工作:

javascript 复制代码
// app/actions.js
'use server';

export async function addComment(prevState, formData) {
  const comment = formData.get('comment');
  const postId = formData.get('postId');
  
  if (!comment.trim()) {
    return { error: '评论不能为空' };
  }
  
  await db.comment.create({
    data: {
      content: comment,
      postId: parseInt(postId)
    }
  });
  
  revalidatePath(`/posts/${postId}`);
  return { success: '评论已添加' };
}
javascript 复制代码
// 组件中使用
export default function CommentForm({ postId }) {
  const [state, formAction, isPending] = useActionState(addComment, null);
  
  return (
    <form action={formAction}>
      <input type="hidden" name="postId" value={postId} />
      <textarea 
        name="comment" 
        placeholder="写下您的评论..."
        required
      />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '添加评论'}
      </button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">{state.success}</p>}
    </form>
  );
}

现代 Suspense 组件 {#modern-suspense}

Suspense 的核心概念

现代 Suspense 主要用于:

  1. 数据获取:等待异步数据加载
  2. 代码分割:动态导入组件
  3. 服务器端渲染:流式渲染

数据获取与 Suspense

使用 use Hook(React 19)

javascript 复制代码
import { Suspense, use } from 'react';

function UserProfile({ userPromise }) {
  const user = use(userPromise);
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>加入时间:{user.joinDate}</p>
    </div>
  );
}

export default function App() {
  const userPromise = fetch('/api/user/123').then(res => res.json());
  
  return (
    <div>
      <h1>用户信息</h1>
      <Suspense fallback={<div>加载用户信息中...</div>}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
    </div>
  );
}

服务器组件中的 Suspense

在服务器组件中,async 组件会自动与 Suspense 集成:

javascript 复制代码
// app/page.js
import { Suspense } from 'react';

async function SlowComponent() {
  // 模拟慢速数据获取
  await new Promise(resolve => setTimeout(resolve, 2000));
  const data = await fetch('https://api.example.com/data');
  return <div>慢速组件数据:{JSON.stringify(await data.json())}</div>;
}

async function FastComponent() {
  // 快速数据获取
  await new Promise(resolve => setTimeout(resolve, 500));
  return <div>快速组件已加载</div>;
}

export default function Page() {
  return (
    <div>
      <h1>我的页面</h1>
      
      {/* 快速组件 */}
      <Suspense fallback={<div>加载快速内容...</div>}>
        <FastComponent />
      </Suspense>
      
      {/* 慢速组件 */}
      <Suspense fallback={<div>加载慢速内容...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

嵌套和并发 Suspense

javascript 复制代码
function App() {
  return (
    <div>
      <Suspense fallback={<div>加载应用...</div>}>
        <Header />
        
        <main>
          <Suspense fallback={<div>加载主要内容...</div>}>
            <MainContent />
            
            <aside>
              <Suspense fallback={<div>加载侧边栏...</div>}>
                <Sidebar />
              </Suspense>
            </aside>
          </Suspense>
        </main>
        
        <Suspense fallback={<div>加载页脚...</div>}>
          <Footer />
        </Suspense>
      </Suspense>
    </div>
  );
}

错误边界与 Suspense

javascript 复制代码
import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>出错了:</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>重试</button>
    </div>
  );
}

export default function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Suspense fallback={<div>加载中...</div>}>
        <DataComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

React 服务器组件(RSC) {#server-components}

服务器组件基础

服务器组件在服务器上执行,可以直接访问服务器资源。

基本服务器组件

javascript 复制代码
// app/posts/page.js(服务器组件)
import { db } from '@/lib/database';

export default async function PostsPage() {
  // 直接在服务器上查询数据库
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  });
  
  return (
    <div>
      <h1>最新文章</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
          <time>{new Date(post.createdAt).toLocaleDateString()}</time>
        </article>
      ))}
    </div>
  );
}

服务器和客户端组件混合

服务器组件中嵌入客户端组件

javascript 复制代码
// app/post/[id]/page.js(服务器组件)
import { db } from '@/lib/database';
import LikeButton from './LikeButton';
import Comments from './Comments';

export default async function PostPage({ params }) {
  const post = await db.post.findUnique({
    where: { id: params.id },
    include: { 
      author: true,
      _count: { select: { likes: true, comments: true } }
    }
  });
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>作者:{post.author.name}</p>
      <div>{post.content}</div>
      
      {/* 客户端交互组件 */}
      <LikeButton 
        postId={post.id} 
        initialLikes={post._count.likes} 
      />
      
      {/* 混合组件:服务器获取数据,客户端交互 */}
      <Comments postId={post.id} />
    </article>
  );
}

// app/post/[id]/LikeButton.js(客户端组件)
'use client';

import { useState, useTransition } from 'react';
import { likePost } from './actions';

export default function LikeButton({ postId, initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  const [isPending, startTransition] = useTransition();
  
  const handleLike = () => {
    startTransition(async () => {
      const result = await likePost(postId);
      if (result.success) {
        setLikes(result.likes);
      }
    });
  };
  
  return (
    <button onClick={handleLike} disabled={isPending}>
      👍 {likes} {isPending ? '...' : ''}
    </button>
  );
}

数据获取模式

串行数据获取

javascript 复制代码
// 依赖关系的数据获取
export default async function UserDashboard({ params }) {
  // 首先获取用户信息
  const user = await getUser(params.userId);
  
  // 基于用户信息获取相关数据
  const userPreferences = await getUserPreferences(user.id);
  const userPosts = await getUserPosts(user.id);
  
  return (
    <div>
      <UserProfile user={user} />
      <UserSettings preferences={userPreferences} />
      <UserPosts posts={userPosts} />
    </div>
  );
}

并行数据获取

javascript 复制代码
export default async function Dashboard() {
  // 并行获取独立的数据
  const [stats, recentPosts, users] = await Promise.all([
    getStats(),
    getRecentPosts(),
    getUsers()
  ]);
  
  return (
    <div>
      <StatsCards stats={stats} />
      <RecentPosts posts={recentPosts} />
      <UsersList users={users} />
    </div>
  );
}

流式数据获取

javascript 复制代码
import { Suspense } from 'react';

async function ExpensiveComponent() {
  // 耗时操作
  const data = await fetchExpensiveData();
  return <div>{data}</div>;
}

export default function Page() {
  return (
    <div>
      <h1>快速加载的内容</h1>
      
      {/* 这部分会流式渲染 */}
      <Suspense fallback={<div>加载昂贵数据中...</div>}>
        <ExpensiveComponent />
      </Suspense>
    </div>
  );
}

服务器函数(Server Functions) {#server-functions}

Server Actions

Server Actions 是可以在客户端调用的服务器函数。

基础用法

javascript 复制代码
// app/actions.js
'use server';

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

export async function createUser(formData) {
  const userData = {
    name: formData.get('name'),
    email: formData.get('email')
  };
  
  // 服务器端验证
  if (!userData.email.includes('@')) {
    return { error: '请输入有效的邮箱地址' };
  }
  
  try {
    const user = await db.user.create({
      data: userData
    });
    
    // 重新验证缓存
    revalidateTag('users');
    
    // 重定向到用户列表
    redirect('/users');
    
  } catch (error) {
    return { error: '创建用户失败' };
  }
}

export async function updateUserProfile(userId, formData) {
  const updates = {
    name: formData.get('name'),
    bio: formData.get('bio')
  };
  
  await db.user.update({
    where: { id: userId },
    data: updates
  });
  
  revalidateTag(`user-${userId}`);
  return { success: true };
}

内联 Server Actions

javascript 复制代码
export default function TodoForm() {
  async function addTodo(formData) {
    'use server';
    
    const title = formData.get('title');
    
    if (!title.trim()) {
      return;
    }
    
    await db.todo.create({
      data: { title, completed: false }
    });
    
    revalidatePath('/todos');
  }
  
  return (
    <form action={addTodo}>
      <input type="text" name="title" placeholder="添加待办事项" />
      <button type="submit">添加</button>
    </form>
  );
}

Server Functions 在组件中的使用

javascript 复制代码
// app/profile/page.js
import { getUser } from './actions';

async function ProfileForm({ userId }) {
  const user = await getUser(userId);
  
  async function updateProfile(formData) {
    'use server';
    
    const updates = {
      name: formData.get('name'),
      email: formData.get('email'),
      bio: formData.get('bio')
    };
    
    await db.user.update({
      where: { id: userId },
      data: updates
    });
    
    revalidatePath('/profile');
  }
  
  return (
    <form action={updateProfile}>
      <input 
        type="text" 
        name="name" 
        defaultValue={user.name}
        placeholder="姓名"
      />
      <input 
        type="email" 
        name="email" 
        defaultValue={user.email}
        placeholder="邮箱"
      />
      <textarea 
        name="bio" 
        defaultValue={user.bio || ''}
        placeholder="个人简介"
      />
      <button type="submit">更新资料</button>
    </form>
  );
}

高级服务器函数模式

带验证的服务器函数

javascript 复制代码
'use server';

import { z } from 'zod';

const CreatePostSchema = z.object({
  title: z.string().min(1, '标题不能为空'),
  content: z.string().min(10, '内容至少10个字符'),
  category: z.enum(['tech', 'lifestyle', 'travel'])
});

export async function createPost(prevState, formData) {
  // 验证数据
  const validatedFields = CreatePostSchema.safeParse({
    title: formData.get('title'),
    content: formData.get('content'),
    category: formData.get('category')
  });
  
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }
  
  const { title, content, category } = validatedFields.data;
  
  try {
    await db.post.create({
      data: { title, content, category }
    });
    
    revalidatePath('/posts');
    return { success: true };
    
  } catch (error) {
    return {
      errors: {
        _form: ['创建文章失败']
      }
    };
  }
}

带权限检查的服务器函数

javascript 复制代码
'use server';

import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';

export async function deletePost(postId) {
  const session = await auth();
  
  if (!session?.user) {
    redirect('/login');
  }
  
  const post = await db.post.findUnique({
    where: { id: postId },
    select: { authorId: true }
  });
  
  if (post?.authorId !== session.user.id) {
    throw new Error('无权限删除此文章');
  }
  
  await db.post.delete({
    where: { id: postId }
  });
  
  revalidatePath('/posts');
  return { success: true };
}

最佳实践与使用场景 {#best-practices}

何时使用服务器组件

适合使用服务器组件

  • 数据获取(数据库查询、API 调用)
  • 访问敏感信息(API 密钥、数据库凭证)
  • 减少客户端 bundle 大小
  • SEO 优化需求
javascript 复制代码
// ✅ 好的服务器组件使用
export default async function ProductList({ category }) {
  const products = await db.product.findMany({
    where: { category },
    include: { reviews: true }
  });
  
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

何时使用客户端组件

适合使用客户端组件

  • 交互性(事件处理、状态管理)
  • 浏览器 API(localStorage、geolocation)
  • React Hooks 使用
  • 实时更新
javascript 复制代码
// ✅ 好的客户端组件使用
'use client';

import { useState, useEffect } from 'react';

export default function SearchBox({ onSearch }) {
  const [query, setQuery] = useState('');
  
  useEffect(() => {
    const debounced = setTimeout(() => {
      onSearch(query);
    }, 300);
    
    return () => clearTimeout(debounced);
  }, [query, onSearch]);
  
  return (
    <input 
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="搜索..."
    />
  );
}

Suspense 最佳实践

javascript 复制代码
// ✅ 合理的 Suspense 边界
export default function BlogPage() {
  return (
    <div>
      {/* 快速加载的导航 */}
      <Navigation />
      
      {/* 主要内容使用 Suspense */}
      <main>
        <Suspense fallback={<PostListSkeleton />}>
          <PostList />
        </Suspense>
        
        <aside>
          <Suspense fallback={<SidebarSkeleton />}>
            <Sidebar />
          </Suspense>
        </aside>
      </main>
    </div>
  );
}

Server Actions 最佳实践

javascript 复制代码
// ✅ 良好的错误处理和验证
'use server';

export async function updateSettings(prevState, formData) {
  try {
    // 验证用户权限
    const session = await getSession();
    if (!session) {
      return { error: '请先登录' };
    }
    
    // 验证数据
    const settings = validateSettings(formData);
    if (!settings.success) {
      return { error: settings.error };
    }
    
    // 更新数据
    await db.userSettings.update({
      where: { userId: session.userId },
      data: settings.data
    });
    
    // 重新验证相关页面
    revalidatePath('/settings');
    
    return { success: '设置已更新' };
    
  } catch (error) {
    return { error: '更新失败,请重试' };
  }
}

性能优化策略

  1. 合理使用缓存
javascript 复制代码
// 使用 Next.js 缓存
export async function getStaticData() {
  const data = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 } // 1小时缓存
  });
  return data.json();
}
  1. 流式渲染优化
javascript 复制代码
export default function Page() {
  return (
    <div>
      <Header />
      
      {/* 立即显示的内容 */}
      <QuickContent />
      
      {/* 流式渲染的慢内容 */}
      <Suspense fallback={<Skeleton />}>
        <SlowContent />
      </Suspense>
    </div>
  );
}
  1. 并行数据获取
javascript 复制代码
export default async function Dashboard() {
  // 所有数据并行获取
  const [user, posts, stats] = await Promise.all([
    getUser(),
    getPosts(),
    getStats()
  ]);
  
  return (
    <div>
      <UserInfo user={user} />
      <PostList posts={posts} />
      <Statistics stats={stats} />
    </div>
  );
}

这些现代 React 特性的组合使用能够创建更高性能、更好用户体验的应用程序,同时保持代码的简洁性和可维护性。

相关推荐
WeiXiao_Hyy12 小时前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡12 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone12 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_090112 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农13 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king13 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳13 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵14 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星14 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_14 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js