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
,那么这个文件下所有的子组件就算不加上这个指令,那它也是客户端组件了,为此我们需要合理规划组件。
如下的 <MyComponent />
实际上是客户端组件。
"use client";
import { useState } from "react";
import MyComponent from "./MyComponent";
export default function Home() {
const [num, setNum] = useState(0);
return (
<main>
<h1>{num}</h1>
<button onClick={() => setNum(num + 1)}>+1</button>
<MyComponent />
</main>
);
}
import { useEffect } from "react";
const MyComponent = () => {
useEffect(() => {
console.log("client component");
}, []);
return <div>123</div>;
};
export default MyComponent;
另外目前很大一部分第三方库都没有支持 use client
,为此会出现如下错误。
为了能够正常在 rsc 中使用我们需要进行一些特殊处理,下面以 framer-motion 为例,因为它一般都会包裹在组件的最外层。
"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;
或者可以封装一下
"use client"
import { motion } from "framer-motion";
export const MotionDiv = motion.div;
数据获取
fetch 数据是 rsc 中重要的一环,我们可以如下编写
export default async function Home() {
const data = await fetch("https://api.github.com/repos/vercel/next.js").then(
(res) => res.json()
);
return <div>{data.id}</div>;
}
但注意这么写默认是会缓存数据的,因为NextJS 对原生 fetch 进行了些修改,如果要想变为动态的话,我们得添加下 cache
的配置
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 <div>{data.id}</div>;
也可以变为 每 10 秒重新获取一次
// 每 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 <div>{data.id}</div>;
}
路由
App Router 的路由在原先的基础上进行了增强,我们可以通过 (folderName)
对路由进行分组,在括号中的组名并不会被映射到实际的路由上,在提高代码可读性的同时,也可以共享 Layout。
动态路由方面和之前差不多通过 [folderName]
来定义,不过现在可以直接在 props 中获取对于的值
我们也可以创建 loading.tsx
,其就是包了一层 Suspense,当我们在 page.tsx
fetch 数据的过程中,可以显示 loading.tsx
中的内容。
同时也有 error.tsx
,当页面渲染出现错误时,也可以及时兜底,避免那串「白屏黑字」。
还有一种 Parallel Routes
我们可以把@folderName
给映射到到 layout 的 props 里,当成「插槽」来使用,一般可以适用于网页顶部的 header,和底部 footer 之类的。
export default function Layout(props: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<>
{props.children}
{props.team}
{props.analytics}
</>
);
}
最后
总之 App Router 中的 API 相当的多,目前列举的也只是一小部分而已,具体的还是以官方文档为主吧。