Next.js 数据获取
Next.js 提供了多种数据获取方法,支持不同的渲染策略和使用场景。本章将详细介绍各种数据获取方式及其最佳实践。
数据获取概述
Next.js 支持以下几种主要的数据获取方式:
- getStaticProps - 静态生成时获取数据
- getServerSideProps - 服务端渲染时获取数据
- getStaticPaths - 动态路由的静态生成
- 客户端数据获取 - 使用 useEffect 或 SWR/React Query
- App Router 数据获取 - Next.js 13+ 的新方式
getStaticProps - 静态生成
getStaticProps 在构建时运行,适用于数据不经常变化的页面。
基本用法
javascript
// pages/blog.js
export default function Blog({ posts }) {
return (
<div>
<h1>博客文章</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
export async function getStaticProps() {
// 从 API 或文件系统获取数据
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return {
props: {
posts,
},
// 可选:重新验证时间(秒)
revalidate: 60, // ISR - 增量静态再生
}
}高级配置
javascript
export async function getStaticProps(context) {
const { params, preview, previewData, locale } = context
try {
const posts = await fetchPosts()
return {
props: {
posts,
},
// 重新验证设置
revalidate: 3600, // 1小时
// 404 处理
notFound: posts.length === 0,
// 重定向
redirect: posts.length === 0 ? {
destination: '/no-posts',
permanent: false,
} : undefined,
}
} catch (error) {
return {
notFound: true,
}
}
}getServerSideProps - 服务端渲染
getServerSideProps 在每次请求时运行,适用于需要实时数据的页面。
基本用法
javascript
// pages/dashboard.js
export default function Dashboard({ user, stats }) {
return (
<div>
<h1>欢迎,{user.name}</h1>
<div>
<p>今日访问量:{stats.todayVisits}</p>
<p>总用户数:{stats.totalUsers}</p>
</div>
</div>
)
}
export async function getServerSideProps(context) {
const { req, res, query, params } = context
// 获取用户信息(基于 cookie 或 session)
const user = await getUserFromRequest(req)
if (!user) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
// 获取实时统计数据
const stats = await fetchUserStats(user.id)
return {
props: {
user,
stats,
},
}
}错误处理和缓存
javascript
export async function getServerSideProps(context) {
const { res } = context
try {
const data = await fetchData()
// 设置缓存头
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: { data },
}
} catch (error) {
console.error('数据获取失败:', error)
return {
props: {
error: '数据加载失败',
},
}
}
}getStaticPaths - 动态路由
用于动态路由的静态生成,定义哪些路径需要预渲染。
基本用法
javascript
// pages/posts/[id].js
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
export async function getStaticPaths() {
// 获取所有文章 ID
const posts = await fetchAllPosts()
const paths = posts.map(post => ({
params: { id: post.id.toString() }
}))
return {
paths,
fallback: false, // 404 for non-existent paths
}
}
export async function getStaticProps({ params }) {
const post = await fetchPost(params.id)
if (!post) {
return {
notFound: true,
}
}
return {
props: { post },
revalidate: 3600,
}
}Fallback 策略
javascript
export async function getStaticPaths() {
// 只预渲染最受欢迎的文章
const popularPosts = await fetchPopularPosts()
const paths = popularPosts.map(post => ({
params: { id: post.id.toString() }
}))
return {
paths,
fallback: 'blocking', // 其他路径在首次请求时生成
}
}
// 处理 fallback 状态
export default function Post({ post }) {
const router = useRouter()
if (router.isFallback) {
return <div>加载中...</div>
}
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}客户端数据获取
使用 useEffect
javascript
import { useState, useEffect } from 'react'
export default function Profile() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchUser() {
try {
const response = await fetch('/api/user')
if (!response.ok) {
throw new Error('获取用户信息失败')
}
const userData = await response.json()
setUser(userData)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
fetchUser()
}, [])
if (loading) return <div>加载中...</div>
if (error) return <div>错误:{error}</div>
if (!user) return <div>未找到用户</div>
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}使用 SWR
SWR 是一个用于数据获取的 React Hook 库,提供缓存、重新验证等功能。
javascript
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then(res => res.json())
export default function Profile() {
const { data: user, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>加载失败</div>
if (isLoading) return <div>加载中...</div>
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}SWR 高级用法
javascript
import useSWR, { mutate } from 'swr'
export default function TodoList() {
const { data: todos, error } = useSWR('/api/todos', fetcher, {
refreshInterval: 1000, // 每秒刷新
revalidateOnFocus: false, // 焦点时不重新验证
dedupingInterval: 2000, // 2秒内的重复请求会被去重
})
const addTodo = async (text) => {
// 乐观更新
const newTodo = { id: Date.now(), text, completed: false }
mutate('/api/todos', [...todos, newTodo], false)
try {
await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
})
// 重新验证数据
mutate('/api/todos')
} catch (error) {
// 回滚乐观更新
mutate('/api/todos')
}
}
return (
<div>
{todos?.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}App Router 数据获取 (Next.js 13+)
Next.js 13 引入了新的 App Router,提供了更简洁的数据获取方式。
服务端组件数据获取
javascript
// app/posts/page.js
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // 缓存1小时
})
if (!res.ok) {
throw new Error('获取文章失败')
}
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
<h1>文章列表</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}并行数据获取
javascript
// app/dashboard/page.js
async function getUser() {
const res = await fetch('https://api.example.com/user')
return res.json()
}
async function getStats() {
const res = await fetch('https://api.example.com/stats')
return res.json()
}
export default async function Dashboard() {
// 并行获取数据
const [user, stats] = await Promise.all([
getUser(),
getStats()
])
return (
<div>
<h1>欢迎,{user.name}</h1>
<div>访问量:{stats.visits}</div>
</div>
)
}流式渲染和 Suspense
javascript
// app/posts/page.js
import { Suspense } from 'react'
async function PostList() {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
)
}
export default function PostsPage() {
return (
<div>
<h1>文章列表</h1>
<Suspense fallback={<div>加载文章中...</div>}>
<PostList />
</Suspense>
</div>
)
}数据获取最佳实践
1. 选择合适的数据获取方法
javascript
// 静态内容 - 使用 getStaticProps
export async function getStaticProps() {
const posts = await fetchBlogPosts()
return { props: { posts }, revalidate: 3600 }
}
// 用户特定内容 - 使用 getServerSideProps
export async function getServerSideProps(context) {
const user = await getUserFromSession(context.req)
return { props: { user } }
}
// 实时更新内容 - 使用客户端获取
function LiveData() {
const { data } = useSWR('/api/live-data', fetcher, {
refreshInterval: 1000
})
return <div>{data?.value}</div>
}2. 错误处理
javascript
// 统一错误处理
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
console.error('数据获取失败:', error)
throw error
}
}
// 在组件中使用
export async function getStaticProps() {
try {
const data = await fetchWithErrorHandling('/api/data')
return { props: { data } }
} catch (error) {
return {
props: { error: error.message },
revalidate: 60, // 1分钟后重试
}
}
}3. 性能优化
javascript
// 数据预取
import { useRouter } from 'next/router'
import Link from 'next/link'
function PostLink({ post }) {
const router = useRouter()
return (
<Link
href={`/posts/${post.id}`}
onMouseEnter={() => {
// 预取页面数据
router.prefetch(`/posts/${post.id}`)
}}
>
{post.title}
</Link>
)
}
// 条件数据获取
export async function getServerSideProps(context) {
const { user } = context.req
// 只为登录用户获取数据
if (!user) {
return {
redirect: { destination: '/login', permanent: false }
}
}
const userData = await fetchUserData(user.id)
return { props: { userData } }
}4. 缓存策略
javascript
// ISR 缓存策略
export async function getStaticProps() {
const data = await fetchData()
return {
props: { data },
revalidate: 60, // 60秒后重新生成
}
}
// 客户端缓存
const { data } = useSWR('/api/data', fetcher, {
dedupingInterval: 2000, // 2秒内去重
focusThrottleInterval: 5000, // 5秒内限制焦点重新验证
errorRetryInterval: 5000, // 错误重试间隔
})总结
Next.js 提供了灵活的数据获取方案:
- getStaticProps: 适用于静态内容,构建时获取数据
- getServerSideProps: 适用于动态内容,每次请求时获取数据
- getStaticPaths: 用于动态路由的静态生成
- 客户端获取: 适用于用户交互和实时数据
- App Router: Next.js 13+ 的新数据获取方式
选择合适的方法取决于你的具体需求:数据更新频率、SEO 要求、性能考虑等。合理使用这些方法可以构建高性能、用户体验良好的应用程序。