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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。