suemor

suemor

前端萌新
telegram
github
twitter

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 /> 實際上是客戶端組件。

"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,為此會出現如下錯誤。

error-image

為了能夠正常在 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>;
}

bundle

但注意這麼寫默認是 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。

Route Groups with Opt-in Layouts

動態路由方面和之前差不多通過 [folderName] 來定義,不過現在可以直接在 props 中獲取對應的值

20230511011319912

我們也可以創建 loading.tsx ,其就是包了一層 Suspense,當我們在 page.tsx fetch 數據的過程中,可以顯示 loading.tsx 中的內容。

同時也有 error.tsx,當頁面渲染出現錯誤時,也可以及時兜底,避免那串「白屏黑字」。

image-20230511005515155

還有一種 Parallel Routes 我們可以把 @folderName 給映射到到 layout 的 props 裡,當成「插槽」來使用,一般可以適用於網頁頂部的 header,和底部 footer 之類的。

Parallel Routes Diagram

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

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。