Logo
Section 1 / 4

React Server Components 与 Client Components

了解何时使用服务器组件和客户端组件来构建快速、交互式的 Next.js 应用程序。

导入

React Server Components 与 Client Components

了解何时使用服务器组件和客户端组件来构建快速、交互式的 Next.js 应用程序。

在 Server Component 中获取数据

1// app/posts/page.js 2 3async function getPosts() { 4 // 此 fetch 在服务器上运行,并由 Next.js 自动缓存。 5 const res = await fetch('https://jsonplaceholder.typicode.com/posts'); 6 if (!res.ok) { 7 throw new Error('Failed to fetch posts'); 8 } 9 return res.json(); 10} 11 12export default async function PostsPage() { 13 const posts = await getPosts(); 14 15 return ( 16 <main> 17 <h1>博客文章</h1> 18 <ul> 19 {posts.map((post) => ( 20 <li key={post.id}>{post.title}</li> 21 ))} 22 </ul> 23 </main> 24 ); 25}

代码示例

1'use client';

创建一个交互式的 Client Component

1// app/components/CounterButton.js 2 3'use client'; 4 5import { useState } from 'react'; 6 7export default function CounterButton() { 8 const [count, setCount] = useState(0); 9 10 return ( 11 <button onClick={() => setCount(count + 1)}> 12 点击次数: {count} 13 </button> 14 ); 15}
...
React 组件模型中的服务端到客户端数据流

No configuration found for ID: f95e6e4c26699e75ae7e7f4d5c7a4fe68490fd8fa41f70e16a2c8aca892696a6

No configuration found for ID: 9e9e46240fa671928df299088f983346a36173fc3f2c30effd65e2b852c8e02f

包含客户端钩子的无效 Server Component

1import { useState, useEffect } from 'react' 2 3export default function ProductPage() { 4 const [product, setProduct] = useState(null) 5 6 useEffect(() => { 7 fetch('https://world.openfoodfacts.org/api/v0/product/737628064502.json') 8 .then(res => res.json()) 9 .then(data => setProduct(data.product)) 10 }, []) 11 12 if (!product) return <p>加载中...</p> 13 14 return ( 15 <div> 16 <h1>{product.product_name}</h1> 17 <p>{product.generic_name}</p> 18 <p>{product.brand_owner}</p> 19 </div> 20 ) 21}

No configuration found for ID: fae7e4f9e706a7cb80d8c4ff559a22944c1a7ad083afff0bfa3aa588c13900f5

显示解决方案

让我们逐步讲解完整的解决方案。

1. Server Component

我们在服务器上获取产品数据。

在服务器上获取产品数据

1// app/product/page.js 2import ProductDisplay from './ProductDisplay' 3 4export default async function ProductPage() { 5 const res = await fetch('https://world.openfoodfacts.org/api/v0/product/737628064502.json') 6 7 if (!res.ok) throw new Error('Failed to fetch product') 8 9 const data = await res.json() 10 const p = data.product; 11 12 const product = { 13 name: p.product_name || "未知产品", 14 type: p.generic_name || "食品类别", 15 company: p.brand_owner || "公司名称" 16 } 17 18 return <ProductDisplay product={product} /> 19}

说明:

  • 第 5 行: 此处使用 await fetch() 在异步 Server Component 内从 OpenFoodFacts API 获取真实的产品数据。

  • 第 7 行: 检查响应是否有效;如果 fetch 失败则抛出错误。

  • 第 9 行: 将 API 响应解析为 JSON 格式。

  • 第 10–16 行: 从 API 响应中提取并结构化产品信息,若某些字段缺失则使用默认值。

  • 第 17 行: 将格式化后的 product 对象传递给 ProductDisplay 客户端组件进行渲染。

2. Client Component

我们使用从服务器传入的 props 来展示产品 UI。

使用客户端交互性渲染 UI

1// app/product/ProductDisplay.js 2'use client' 3 4export default function ProductDisplay({ product }) { 5 return ( 6 <div> 7 <h1>{product.name}</h1> 8 <p>{product.type}</p> 9 <p>{product.company}</p> 10 </div> 11 ) 12}

说明:

  • 第 1 行: 'use client' 标记此为 Client Component,使其可根据需要使用交互功能。

  • 第 3 行: product 作为 prop 从 Server Component 传入。

  • 第 7–8 行: 我们渲染产品信息。

结论

使用 Suspense 边界进行流式传输