- 作者:老汪软件技巧
- 发表时间:2024-12-12 07:56
- 浏览量:91
随着 Web 应用的复杂性不断提高,用户对页面加载速度和交互流畅度的期望也水涨船高。对于开发者来说,仅仅提升实际性能指标已经不够了,如何优化用户的“感知性能”也变得至关重要。加载动画、骨架屏以及逐步展现内容等技术,成为了改善用户体验的关键手段。
Next.js 作为一个强大的 React 全栈框架。引入了更高效的 Loading UI 和 Streaming 功能,让开发者可以更轻松地实现无缝加载体验。这些特性不仅提升了用户体验,还通过流式渲染减少了服务器和客户端的性能压力。
下面我们来感受下 Next.js 中的 Loading UI 和 Streaming,从基础概念到具体实现,再到实际应用场景,全面解读这些功能的核心原理和使用方法。
在正式进入 Loading UI 和 Streaming 之前,我们先来回顾一下 SSR 渲染(如果对这一块不熟悉的话,推荐去看看《掌握 Next.js 渲染机制:如何在 CSR、SSR、SSG 和 ISR 中做出最佳选择》)。使用 SSR,简单来说,就是需要经过一系列的步骤,用户才能查看页面并与之交互。
具体这些步骤是:
这些步骤是连续的、阻塞的。也就是服务器需等所有数据获取完成后才能渲染 HTML,客户端也需等所有组件代码加载完毕后才能对 UI 进行水合:
React 18 为了解决上面这些问题,引入了 Suspense 组件。
Suspense
在 React 中,Suspense 是一个用于处理异步加载的组件,旨在简化代码和改善用户体验。它允许开发者定义组件在加载异步数据或资源时的备用 UI(通常是加载指示器)。
基本用法
import React, { Suspense } from 'react';
function App () {
return (
<Suspense fallback={<div>Loading...div>}>
<OtherComponent />
Suspense>
);
}
你可以将动态组件包装在 Suspense 中,然后向其传递一个 fallback UI,以便在动态组件加载时显示。如果数据请求缓慢,使用 Suspense 流式渲染该组件,不会影响页面其他部分的渲染,更不会阻塞整个页面。
下面我们写一个案例,如何将 Suspense 和 use 结合使用来优雅地处理异步操作,下面是核心代码(这里使用的是 react 19版本):
import { Suspense, use } from "react";
const todo = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
return await res.json();
}
interface TodoItem {
title: string;
completed: boolean;
id: number;
}
const Todo = ({ promise }: { promise: Promise } ) => {
const todoData = use(promise);
return (
<div>
<h2>Todoh2>
<p>{todoData.title}p>
div>
);
}
export default function App() {
return (
<div>
<h2>Apph2>
<Suspense fallback={<p>Loading...p>}>
<Todo promise={todo()} />
Suspense>
div>
);
}
效果如下:
完整代码可以查看 /clin211/rea…。
在 Next.js 中使用 Suspense 组件
在正式演示之前先来创建下项目:
npx create-next-app@latest --use-pnpm
配置如下图:
我们以电商系统的产品详情的产品信息、用户评论和产品推荐等功能模块为例:
import React, { Suspense } from 'react'
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
// 产品信息
const ProductInfo = async () => {
await sleep(2000); // 模拟异步操作
return <h1>product infoh1>
}
// 产品评论
const ProductComments = async () => {
await sleep(3000); // 模拟异步操作
return <h1>product commentsh1>
}
// 产品推荐
const ProductRecommends = async () => {
await sleep(5000); // 模拟异步操作
return <h1>product recommendsh1>
}
export default function page() {
return (
<main>
<Suspense fallback={<h2>Product Info Loading...h2>}><ProductInfo />Suspense>
<Suspense fallback={<h2>Product Comments Loading...h2>}><ProductComments />Suspense>
<Suspense fallback={<h2>Product Recommends Loading...h2>}><ProductRecommends />Suspense>
main>
)
}
上面这段代码,通过模拟不同的加载时间,可以看到不同的加载状态,确保用户在等待时得到反馈。通过 Suspense 实现了异步组件加载时的过渡效果,每个异步组件都有独立的加载指示器。当每个组件的数据或内容加载完成后,相应的组件将被渲染。
从上面的 GIF 图中,可以看出 /product 路由的加载时间变化,从一开始的 2.08s 到最后的 5.07s,上面的代码中,我们设置的最长时间就是 5s,然后查看网络面板查看 /product 路由的详细请求情况如下图:
其中最关键的就是响应头中 transfer-encoding: chunked,表示数据将以一系列分块的形式进行发送。