Next.js 中有两种处理页面加载的方式,一种是 Loading UI 一种是 Streaming。接下来我将介绍这两种的区别,以及实际的业务场景。
当我们进入某个页面时,需要获取页面数据,可能是从数据库读取也有可能是 API 服务,总之这是一个异步任务,我们可以在获取数据过程中提示用户数据正在加载,比如放置一些骨架屏,提升用户的体验。
如果不对这些进行处理,使用体验会大打折扣。
假设我们有一个场景,进入 /posts 页面后获取所有的文章数据。
app/posts/page.tsx
:
import Link from "next/link";const PostsPage = async () => {const posts = await fetch("https://jsonplaceholder.typicode.com/posts").then((response) => response.json());// await new Promise((resolve) => setTimeout(resolve, 2000));return (<div className="p-10 max-w-3xl mx-auto"><ul className="list-decimal">{posts.map((post) => (<li key={post.id} className=""><h2 className="text-xl font-semibold my-2"><Link href={`/posts/${post.id}`} className="hover:underline">{post.title}</Link></h2><p>{post.body}</p></li>))}</ul></div>);
};export default PostsPage;
可以解开上面的注释延长加载时间。
如果我们从首页进入到 /posts 页面,不会立即进入,需要等待 await 之后才会进入页面,这个等待时间就取决于我们的网络了,当网络较差时,可能很久才获取数据,这就大大影响了用户体验。
Loading UI
在 posts 同级目录创建 app/posts/loading.tsx
文件:
(ps:我项目使用的 TypeScript,如果是 JavaScript 则为 jsx 后缀)
const PostsLoading = () => {return <p>Loading...</p>;
};export default PostsLoading;
这样当我们进入 /posts 页面后不会等待数据加载完成后才进入页面,而是直接进入 /posts 页面,数据加载过程中显示的内容为 loading.tsx 文件的内容,我们就可以写一些文字提示或者骨架屏之类的内容。
Streaming (Suspense)
另一种方式,也是比较推荐的方式:Streaming (Suspense),流式传输。
比如我们一个页面有多个异步组件,有文章、评论等等,如果我们使用上述第一种方案,页面会等所有的数据加载完成才渲染,而通过 Suspense 可以对每一个组件异步渲染。
React 18 引入了 Suspense
组件,支持在组件树中展示异步组件。Next.js 利用这一特性,让开发者可以定义一个组件在加载期间显示的备选内容。
通过 Suspense
,你可以在组件等待数据时显示一个备用 UI,这样用户就不会看到不完整的空白界面。一旦数据加载完毕,React 将渲染实际的组件。这种异步加载方式特别适用于大型组件或数据,它们可能需要长时间来加载和渲染。
新建 app/posts2/page.tsx
文件:
import Link from "next/link";
import { Suspense } from "react";const Posts2Page = () => {return (<div><Suspense fallback={<PostsSkeleton />}><PostList /></Suspense></div>);
};const PostsSkeleton = () => {return (<div className="p-10 max-w-3xl mx-auto"><ul className="space-y-2 list-decimal">{[...Array(5).keys()].map((i) => (<li key={i} className="space-y-2"><divclassName="animate-pulse h-7 w-1/2 bg-slate-200 rounded duration-1000"style={{animationDelay: `${i * 0.05}s`,}}></div><divclassName="animate-pulse h-5 w-full bg-slate-200 rounded duration-1000"style={{animationDelay: `${i * 0.05}s`,}}></div><divclassName="animate-pulse h-5 w-3/4 bg-slate-200 rounded duration-1000"style={{animationDelay: `${i * 0.05}s`,}}></div></li>))}</ul></div>);
};const PostList = async () => {const posts = await fetch("https://jsonplaceholder.typicode.com/posts").then((response) => response.json());await new Promise((resolve) => setTimeout(resolve, 1000));return (<div className="p-10 max-w-3xl mx-auto"><ul className="list-decimal">{posts.map((post) => (<li key={post.id} className=""><h2 className="text-xl font-semibold my-2"><Link href={`/posts/${post.id}`} className="hover:underline">{post.title}</Link></h2><p>{post.body}</p></li>))}</ul></div>);
};export default Posts2Page;
Posts2Page 使用 Suspense 组件包裹 PostList 组件。当 PostList 组件在加载数据时,Suspense 会显示它的 fallback 属性,也就是 PostsSkeleton 组件。
PostsSkeleton 是一个骨架屏组件,用于在数据加载时显示。它会显示5个文章的占位符。
PostList 是一个异步组件,从 https://jsonplaceholder.typicode.com/posts 这个 URL 获取文章数据,然后显示出来。
最后,如果文章对您有所帮助,还希望能够点赞、收藏、关注,您的每一次点击都会为我带来无穷的动力来写出更好的文章。