“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 特性的组合使用能够创建更高性能、更好用户体验的应用程序,同时保持代码的简洁性和可维护性。

相关推荐
薛定谔的算法3 小时前
JavaScript数组操作完全指南:从基础到高级
前端·javascript·算法
拜无忧3 小时前
前端,用SVG 模仿毛笔写字绘画,defs,filter
前端·css·svg
怪可爱的地球人3 小时前
ts的迭代器和生成器
前端
FlowGram3 小时前
Flowgram 物料库建设思考与实践
前端
用户22152044278003 小时前
Promise实例方法和async、await语法糖
前端·javascript
普通码农3 小时前
uniapp开发微信小程序使用takeSnapshot截图保存分享
前端
snows_l4 小时前
MacOS 通过Homebrew 安装nvm
前端·macos
前端开发爱好者4 小时前
下一代全栈框架:Deno + Vite 的结合体!
前端·javascript·后端
CUGGZ4 小时前
前端部署,又有新花样?
前端·javascript