NextJS 13.4 App Router 初体验

NextJS 最近发布了 13.4 版本，使 App Router “稳定” 下来，同时官方 CLI 也将 App Router 变更为默认且推荐的方案，但是 App Router 中引入了 React Server Components （以下简称 rsc ）的概念，且变更了相当多的 API，这使其学习难度更加陡峭。

服务端组件

在 App Router 中，NextJS 将会区分 Client Components 和 Server Components， Server Components 是一种特殊的 React 组件，它不是在浏览器端运行，而是只能在服务器端运行。又因为它们没有状态，所以不能使用只存在于客户端的特性（也就是说 useState、useEffect 那些都是用不了的），所以一般我们可以用于获取数据，或者对组件进行渲染（比如你要渲染 markdown 那对应的 JavaScript 依赖就只存在于客户端），从而达到减少客户端体积的作用。

同时 App Router 中的文件默认都是服务端组件，如果你要使用客户端组件那就需要加上 use client，但实际上这个命令时候影响到子组件的，也就是说如果你父组件加上了 use client，那么这个文件下所有的子组件就算不加上这个指令，那它也是客户端组件了，为此我们需要合理规划 Layout，把客户端端组件利用 Layout 给抽离出去。

如下的 <MyComponent /> 实际上是客户端组件。

Copy
"use client";

import { useState } from "react";
import MyComponent from "./MyComponent";

export default function Home() {
const [num, setNum] = useState(0);
return (

{num}
setNum(num + 1)}>+1
<MyComponent />

);
}

Copy
import { useEffect } from "react";

const MyComponent = () => {
useEffect(() => {
console.log("client component");
}, []);
return 123 ;
};

export default MyComponent;

另外目前很大一部分第三方库都没有支持 use client，为此会出现如下错误。

为了能够正常在 rsc 中使用我们需要进行一些特殊处理，下面以 framer-motion 为例，因为它一般都会包裹在组件的最外层。

Copy
"use client"
import { FC, PropsWithChildren } from "react";
import { motion } from "framer-motion";

const MotionLayout: FC<PropsWithChildren> = ({ children }) => {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
};

export default MotionLayout;

或者可以封装一下

Copy
"use client"

import { motion } from "framer-motion";

export const MotionDiv = motion.div;

数据获取

fetch 数据是 rsc 中重要的一环，我们可以如下编写

Copy
export default async function Home() {
const data = await fetch("https://api.github.com/repos/vercel/next.js").then(
(res) => res.json()
);
return {data.id} ;
}

但注意这么写默认是 SSG，因为 NextJS 对原生 fetch 进行了些修改，如果要想变为 SSR 的话，我们得添加下 cache 的配置

Copy
export default async function Home() {
const data = await fetch("https://api.github.com/repos/vercel/next.js", {
cache: "no-store",
}).then((res) => res.json());
return {data.id} ;

如下为 ISR 写法

Copy
// ISR 每 10 秒重新获取一次
export default async function Home() {
const data = await fetch("https://api.github.com/repos/vercel/next.js", {
next: {
revalidate: 10,
},
}).then((res) => res.json());
return {data.id} ;
}

如果你用 axios 之类的话，想进行 SSR 的话，可以这么写

Copy
import axios from "axios";

export const dynamic = "force-dynamic";

export default async function Home() {
const { data } = await axios.get(
"https://api.github.com/repos/vercel/next.js"
);
return {data.id} ;
}

路由

App Router 的路由在原先的基础上进行了增强，我们可以通过 (folderName) 对路由进行分组，在括号中的组名并不会被映射到实际的路由上，在提高代码可读性的同时，也可以共享 Layout。

动态路由方面和之前差不多通过 [folderName] 来定义，不过现在可以直接在 props 中获取对于的值

我们也可以创建 loading.tsx ，其就是包了一层 Suspense，当我们在 page.tsx fetch 数据的过程中，可以显示 loading.tsx 中的内容。

同时也有 error.tsx，当页面渲染出现错误时，也可以及时兜底，避免那串「白屏黑字」。

还有一种 Parallel Routes 我们可以把@folderName给映射到到 layout 的 props 里，当成「插槽」来使用，一般可以适用于网页顶部的 header，和底部 footer 之类的。

Copy
export default function Layout(props: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<>
{props.children}
{props.team}
{props.analytics}
</>
);
}

最后

总之 App Router 中的 API 相当的多，目前列举的也只是一小部分而已，具体的还是以官方文档为主吧。

官方文档

此文由 Mix Space 同步更新至 xLog
原始链接为 https://suemor.com/posts/programming/approuter