PS:本文基于 Next15 的 App router,若版本不同情况可能有所不同
在Nextjs官方文档中,给出了三种不同的数据获取方式
使用 Fetch API 在服务器上获取数据
//app/page.tsx
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
使用ORM或数据库在服务器上获取数据
//app/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
在客户端获取数据
//app/page.tsx
'use client'
import { useState, useEffect } from 'react'
export function Posts() {
const [posts, setPosts] = useState(null)
useEffect(() => {
async function fetchPosts() {
const res = await fetch('https://api.vercel.app/blog')
const data = await res.json()
setPosts(data)
}
fetchPosts()
}, [])
if (!posts) return <div>Loading...</div>
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
官方推荐优先从服务端获取数据,这样能够减少客户端负载,提升性能。因此我针对前两种方法讲一下如何优化。
官方给的示例中,直接将获取数据的代码放在了 page
中,因为获取数据是异步的,所以要给 page
加上 async
,这样整个页面都是异步的,会在数据获取完成后展示 page
。
这里以我用 Nextjs 写的blog网站为例,下面是按官方示例来写的。
这样虽然没问题,但是整个页面都是异步的,尽管页面中的Hero Section是完全静态的内容,也会等待 posts
获取完成后才会加载。
因此我们应该做出些优化,只让与数据有关的section是异步的,让页面的其他内容先加载,那么这里就可以用到 Nextjs 中的 Suspense
,官方介绍如下:
<Suspense> allows you to be more granular about what parts of the page to stream. For example, you can immediately show any page content that falls outside of the <Suspense> boundary, and stream in the list of blog posts inside the boundary.
以我的blog为例,我可以把数据获取逻辑放在PostList
中,将PostList
作为异步函数,然后用Suspense
包裹它,实现流式加载。
但是这样就让代码更加混乱了,因为我只想让PostList
负责UI界面,不涉及数据相关的操作。
为了实现业务分离,我们可以再新建一个异步的 PostListContainer
组件,负责处理数据获取逻辑,并返回带有数据的PostList
组件,如下图所示。
将原本Page
上的async
转移到了PostListContainer
中,这样一来,不仅实现了业务分离,还实现了页面的流式加载。