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 />
實際上是客戶端組件。
"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>;
}
但注意這麼寫默認是 SSG,因為 NextJS 對原生 fetch 進行了一些修改,如果要想變為 SSR 的話,我們得添加下 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>;
}
如下為 ISR 寫法
// 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 <div>{data.id}</div>;
}
如果你用 axios 之類的話,想進行 SSR 的話,可以這麼寫
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 <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 相當的多,目前列舉的也只是一小部分而已,具體的還是以官方文檔為主吧。
此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://suemor.com/posts/programming/approuter