React 服务器组件深度解析
React 服务器组件 (RSC) 是 React 18 的新特性。
什么是服务器组件?
服务器组件是可以在服务器端渲染的组件,默认在服务器上执行。
基础用法
async function Page() { const data = await fetch('https://api.example.com/users'); const users = await data.json();
return ( <div> <h1>用户列表</h1> {users.map(user => ( <div key={user.id}>{user.name}</div> ))} </div> ); }
export default Page;
|
服务器组件 vs 客户端组件
服务器组件
async function ServerComponent() { const data = await fetch('/api/data'); return <div>{data}</div>; }
export default ServerComponent;
|
客户端组件
'use client';
import { useState } from 'react';
function ClientComponent() { const [count, setCount] = useState(0);
return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}> 增加 </button> </div> ); }
export default ClientComponent;
|
数据获取
在服务器组件中
async function Page() { const res = await fetch('https://api.example.com/users', { cache: 'no-store' }); const users = await res.json();
return ( <div> <h1>用户列表</h1> {users.map(user => ( <UserCard key={user.id} user={user} /> ))} </div> ); }
function UserCard({ user }) { return <div>{user.name}</div>; }
|
在客户端组件中
'use client';
import { useState, useEffect } from 'react';
function ClientComponent() { const [users, setUsers] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { fetch('/api/users') .then(res => res.json()) .then(setUsers) .finally(() => setLoading(false)); }, []);
if (loading) return <p>加载中...</p>;
return ( <div> <h1>用户列表</h1> {users.map(user => ( <div key={user.id}>{user.name}</div> ))} </div> ); }
|
并发特性
并发数据获取
async function Page() { const [users, posts] = await Promise.all([ fetch('/api/users').then(res => res.json()), fetch('/api/posts').then(res => res.json()) ]);
return ( <div> <h1>用户列表</h1> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul>
<h1>帖子列表</h1> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ); }
|
组件通信
服务器组件传递给客户端组件
async function UserList() { const users = await fetch('/api/users').then(res => res.json());
return ( <> {users.map(user => ( <UserProfile key={user.id} user={user} /> ))} </> ); }
function UserProfile({ user }) { return <div>{user.name}</div>; }
|
客户端组件传递给服务器组件
'use client';
import { useState } from 'react';
function SearchForm() { const [query, setQuery] = useState('');
return ( <form action="/api/search"> <input name="q" value={query} onChange={(e) => setQuery(e.target.value)} /> <button type="submit">搜索</button> </form> ); }
async function Page({ searchParams }) { const query = searchParams?.q || '';
if (query) { const results = await fetch(`/api/search?q=${query}`).then(res => res.json()); return <div>搜索结果: {results}</div>; }
return <SearchForm />; }
|
缓存策略
缓存控制
async function Page() { const data = await fetch('/api/data');
const data = await fetch('/api/data', { cache: 'force-cache' });
const data = await fetch('/api/data', { cache: 'no-store' });
const data = await fetch('/api/data', { cache: 'force-cache', next: { revalidate: 60 } });
return <div>{data}</div>; }
|
客户端 Hooks
使用客户端 Hooks
'use client';
import { useState, useEffect } from 'react';
function ClientComponent() { const [time, setTime] = useState(new Date());
useEffect(() => { const timer = setInterval(() => { setTime(new Date()); }, 1000);
return () => clearInterval(timer); }, []);
return <p>{time.toLocaleTimeString()}</p>; }
async function Page() { const now = new Date();
return ( <div> <p>服务器时间: {now.toLocaleTimeString()}</p> <ClientComponent /> </div> ); }
|
代码分割
动态导入
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), { loading: () => <p>加载中...</p>, ssr: false });
export default function Page() { return <HeavyComponent />; }
|
Props 传递
传递原始数据
async function PostList() { const posts = await fetch('/api/posts').then(res => res.json());
return ( <div> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); }
function PostCard({ post }) { return ( <div> <h2>{post.title}</h2> <p>{post.content}</p> </div> ); }
|
传递回调函数
'use client';
import { useState } from 'react';
function Counter({ onIncrement }) { const [count, setCount] = useState(0);
const handleClick = () => { onIncrement(count); setCount(c => c + 1); };
return <button onClick={handleClick}>点击</button>; }
async function Page() { const handleIncrement = async (count) => { const res = await fetch('/api/increment', { method: 'POST', body: JSON.stringify({ count }) }); return res.json(); };
return <Counter onIncrement={handleIncrement} />; }
|
最佳实践
1. 默认使用服务器组件
async function Page() { return <div>内容</div>; }
'use client';
|
2. 数据获取在服务器端
async function Page() { const data = await fetch('/api/data'); return <div>{data}</div>; }
'use client'; function Page() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/data').then(setData); }, []); return <div>{data}</div>; }
|
3. 避免在服务器组件中使用状态
async function Page() { return <ClientComponent />; }
'use client'; function ClientComponent() { const [state, setState] = useState(0); return <div>{state}</div>; }
|
4. 合理使用缓存
async function Page() { const data = await fetch('/api/data', { next: { revalidate: 3600 } }); return <div>{data}</div>; }
|
5. 错误处理
async function Page() { try { const res = await fetch('/api/data'); if (!res.ok) throw new Error('请求失败'); const data = await res.json(); return <div>{data}</div>; } catch (error) { return <div>错误: {error.message}</div>; } }
|
总结
服务器组件优势:
- 更好的性能:减少客户端 JavaScript 体积
- 更少的网络请求:数据直接在服务器获取
- 更安全的敏感数据:不会暴露到客户端
使用策略:
- 默认使用服务器组件
- 只在需要客户端交互时使用 ‘use client’
- 合理利用缓存策略
React 服务器组件让前端开发更加高效!