渲染篇
前言
渲染分成四大类:
- CSR: 客户端渲染
- SSR: 服务器端渲染
- SSG: 静态站点生成
- ISR: 服务器端内容更新
CSR(没有源代码显示)
Next.js 支持 CSR,在 Next.js Pages Router 下有两种方式实现客户端渲染。
第一种 use client
use client
页面会显示,但是右键查看源代码不会有渲染
// pages/csr.js
"use client";
import React, { useState, useEffect } from "react";
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
};
fetchData().catch((e) => {
console.error("An error occurred while fetching the data: ", e);
});
}, []);
return <p>{data ? `Your data: ${JSON.stringify(data)}` : "Loading..."}</p>;
}
第二种 SWR
方法是在客户端使用 NEXT 自己的类库
// pages/csr2.js
import useSWR from "swr";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
export default function Page() {
const { data, error, isLoading } = useSWR(
"https://jsonplaceholder.typicode.com/todos/1",
fetcher
);
if (error) return <p>Failed to load.</p>;
if (isLoading) return <p>Loading...</p>;
return <p>Your Data: {data.title}</p>;
}
SSR(右键查看源代码)(旧)
这里是回顾以下.新版请看后面章节 顾名思义,渲染工作主要在服务端执行。
Page Router 写一个 demo(旧版本)
// pages/ssr.js
export default function Page({ data }) {
return <p>{JSON.stringify(data)}</p>;
}
export async function getServerSideProps() {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const data = await res.json();
return { props: { data } };
}
SSG(基本不用) PageRouter 版本(旧)
简单来说 类似织梦 CMS 那种.现在本地生成好 HTML 页面.要是有改变 就再次重新生成.
适合那种长时间没有数据变化的网站
不获取数据
Next.js 支持 SSG。当不获取数据时,默认使用的就是 SSG。我们使用 Pages Router
写个 demo:
// pages/ssg1.js
function About() {
return <div>About</div>;
}
export default About;
像这种没有数据请求的页面,Next.js 会在构建的时候生成一个单独的 HTML 文件。
不过 Next.js 默认没有导出该文件。如果你想看到构建生成的 HTML 文件,修改 next.config.js 文件:
const nextConfig = {
output: "export",
};
module.exports = nextConfig;
再执行 npm run build
,你就会在根目录下看到生成的 out
文件夹,里面存放了构建生成的 HTML
文件。
获取数据
- 第一种情况,页面内容需要获取数据。就比如博客的文章内容需要调用 API 获取。Next.js 提供了 getStaticProps。写个 demo:
// pages/ssg2.js
export default function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export async function getStaticProps() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await res.json();
return {
props: {
posts,
},
};
}
getStaticProps 会在构建的时候被调用,并将数据通过 props 属性传递给页面。
第二种是页面路径需要的数据
这是什么意思呢?就比如数据库里有 100 篇文章,我肯定不可能自己手动定义 100 个路由,然后预渲染 100 个 HTML 吧。Next.js 提供了 getStaticPaths 用于定义预渲染的路径。它需要搭配动态路由使用。写个 demo:
新建 /pages/post/[id].js
,代码如下:
// /pages/post/[id].js
export default function Blog({ post }) {
return (
<>
<header>{post.title}</header>
<main>{post.body}</main>
</>
);
}
export async function getStaticPaths() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await res.json();
const paths = posts.map((post) => ({
params: { id: String(post.id) },
}));
// { fallback: false } 意味着当访问其他路由的时候返回 404
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
// 如果路由地址为 /posts/1, params.id 为 1
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
);
const post = await res.json();
return { props: { post } };
}
其中,getStaticPaths 和 getStaticProps 都会在构建的时候被调用,getStaticPaths 定义了哪些路径被预渲染,getStaticProps 获取路径参数,请求数据传给页面。
当你执行 npm run build 的时候,就会看到 post 文件下生成了一堆 HTML 文件:
ISR
增量更新在 SSG 基础上
Next.js 支持 ISR,并且使用的方式很简单。你只用在 getStaticProps 中添加一个 revalidate 即可。我们基于 SSG 的示例代码上进行修改:
// pages/post/[id].js
// 保持不变
export default function Blog({ post }) {
return (
<>
<header>{post.title}</header>
<main>{post.body}</main>
</>
);
}
// fallback 的模式改为 'blocking'
export async function getStaticPaths() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await res.json();
const paths = posts.slice(0, 10).map((post) => ({
params: { id: String(post.id) },
}));
return { paths, fallback: "blocking" };
}
// 使用这种随机的方式模拟数据改变
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
// 多返回了 revalidata 属性
export async function getStaticProps({ params }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${getRandomInt(100)}`
);
const post = await res.json();
return {
props: { post },
revalidate: 10, // 10S后开始更新一次
};
}
revalidate
表示当发生请求的时候,至少间隔多少秒才更新页面。
这听起来有些抽象,以 revalidate
: 10 为例,在初始请求后和接下来的 10 秒内,页面都会使用之前构建的 HTML。10s 后第一个请求发生的时候,依然使用之前编译的 HTML。但 Next.js 会开始构建更新 HTML,从下个请求起就会使用新的 HTML。(如果构建失败了,就还是用之前的,等下次再触发更新)
当你在本地使用 next dev 运行的时候,getStaticProps
会在每次请求的时候被调用。所以如果你要测试 ISR 功能,先构建出生产版本,再运行生产服务。也就是说,测试 ISR 效果,用这俩命令:
你可以看到,页面刷新后,文章内容发生变化。然后 10s 内的刷新,页面内容都没有变化。10s 后的第一次刷新触发了更新,10s 后的第二次刷新内容发生了变化。
注意这次 getStaticPaths 函数的返回为 return { paths, fallback: 'blocking' }
。它表示构建的时候就渲染 paths 里的这些路径。如果请求其他的路径,那就执行服务端渲染。在上节 SSG 的例子中,我们设置 fallback 为 false
,它表示如果请求其他的路径,就会返回 404 错误。
所以在这个 ISR demo 中,如果请求了尚未生成的路径,Next.js 会在第一次请求的时候就执行服务端渲染,编译出 HTML 文件,再请求时就从缓存里返回该 HTML 文件。SSG 优雅降级到 SSR。