NextJS 13.4 App Router 初体验

12 个月前
/ ,
996

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

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>;
}

但注意这么写默认是会缓存数据的,因为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。

Route Groups with Opt-in Layouts

Route Groups with Opt-in Layouts

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

20230511011319912

20230511011319912

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

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

image-20230511005515155

image-20230511005515155

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

Parallel Routes Diagram

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 相当的多,目前列举的也只是一小部分而已,具体的还是以官方文档为主吧。

官方文档

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...