大数据

next js Routing 学习笔记 01

React Server Components (RSC)

  • Server Components
  • Client Components

服务端组件(Server Components)

  • Next js 默认视作 服务端组件
  • 服务端组件 可以执行 服务侧的任务,比如文件读取或者读写数据库
  • 但是不能处理用户交互

客户端组件(Client Components)

  • 需要明确首行使用 use client 标识
  • 不能执行服务端任务 比如读取文件
  • 但是可以与用户交互

路由

基于文件系统的路由系统。

  • 所有的路由必须在/app路径下
  • 路由文件命名:page.js或者page.jsx
  • 每层文件夹代表url一部分路径

嵌套路由

动态路由

获取动态路由参数

type Props = {
  params: Promise<{ productId: string }>;
};

export default async function ProductDetails({ params }: Props) {
  const productId = (await params).productId;
  return <h1>Details about product {productId}</h1>;
}
  • async 标记异步函数
  • {params} 函数传参 参数类型中指定需要的参数名称和类型Promise<{ productId: string }>
  • await params获取参数

嵌套动态路由

获取嵌套路由参数

export default async function ProductReview({
  params,
}: {
  params: Promise<{ productId: string; reviewId: string }>;
}) {
  const { productId, reviewId } = await params;
  return (
    <h1>
      Review {reviewId} for product {productId}
    </h1>
  );
}
  • 同样在参数类型中指定嵌套参数Promise<{ productId: string; reviewId: string }>

获取路由所有分段 catch all segments

文件夹命名格式[...slug]

获取路由参数

export default async function Doc({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;
  if (slug?.length === 2) {
    return (
      <h1>
        Viewing docs for feature {slug[0]} and concept {slug[1]}
      </h1>
    );
  } else if (slug?.length === 1) {
    return <h1>Viewing docs for feature {slug[0]}</h1>;
  }
  return <h1>Docs home page</h1>;
}
  • 参数类型 slug: string[] 每一个元素来自url:http://xxx/ 参数1 / 参数2 /
  • 可以根据参数数组的长度返回不同的页面响应内容

自定义404页面

在page.tsx同级路径新建 not-found.tsx文件 用于显式404页面显式内容

export default function NotFound() {
  return (
    <div>
      <h2>Page Not Found</h2>
      <p>Could not find requested resource</p>
    </div>
  );
}

显式调用404页面

import { notFound } from "next/navigation";

export default async function ProductReview({
  params,
}: {
  params: Promise<{ productId: string; reviewId: string }>;
}) {
  const { productId, reviewId } = await params;
  // 显式调用404页面
  if (parseInt(reviewId) > 1000) {
    notFound();
  }
  
  return (
    <h1>
      Review {reviewId} for product {productId}
    </h1>
  );
}

获取url path信息

"use client";
import { usePathname } from "next/navigation";

export default function NotFound() {
  const pathname = usePathname();
  const productId = pathname.split("/")[2];
  const reviewId = pathname.split("/")[4];
  //   console.log({ pathname, productId, reviewId });
  return (
    <div>
      <h2>
        Review {reviewId} not found for product {productId}
      </h2>
    </div>
  );
}

私有文件夹(/app目录下不想被url访问的文件夹)

  • 格式:下划线 + 文件夹名称: _lib/

路由分组

  • 文件夹命名格式:(分组名称)
  • 分组的文件夹名称不参与到URL path

layout

在子页面指定 自己的布局,子目录下添加layout.tsx

多样的layout

比如登陆页面一个布局,登陆后另一个布局

(main)中根路径有layout有page.tsx 所以访问根URL会定位到该page.tsx

metadata

  • 静态metadata对象
  • 动态生成metadata函数 函数名称必须:generateMetadata

page页面metadata会覆盖layout页面的metadata

定义metadata对象

export const metadata = {
  title: "About Codevolution",
};

动态metadata函数

import { Metadata } from "next";

export const generateMetadata = async ({
  params,
}: Props): Promise<Metadata> => {
  const productId = (await params).productId;
  const title = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(`iPhone ${productId}`);
    }, 100);
  });
  return {
    title: `Product - ${title}`,
  };
};

resolve 是 Promise 的一个核心函数,它的作用是将 Promise 从 pending(等待)状态变为 fulfilled(已完成)状态,并传递一个结果值

resolve(value) 的参数 value 会成为 Promise 的最终结果,这个值可以被 await 或 .then() 接收。

  • new Promise((resolve) => { … }) 创建了一个 Promise,并传入一个执行器函数(executor),这个函数接收 resolve 作为参数。
  • setTimeout 模拟异步操作,100ms 后调用 resolve,并传入字符串 iPhone ${productId}。
  • await 会等待 Promise 完成,并获取 resolve 传入的值,最终赋值给 title。

title Metadata

import { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "Next.js Tutorial - Codevolution",
    template: "%s | Codevolution",
  },
  description: "Generated by Next.js",
};

title属性:default、template、absolute

如果在layout中指定

  • default 默认显式title内容
  • template 使用 %s 占位符 当子路径设置了title会 格式化%s展示内容
  • 如果子页面设置absolute 则忽略layout中的配置

Link组件

    <>
      <Link href="/">Home</Link>
      <h1>Product List</h1>
      <h2>
        <Link href="products/1">Product 1</Link>
      </h2>
      <h2>
        <Link href="products/2">Product 2</Link>
      </h2>
      <h2>
        <Link href="products/3" replace>
          Product 3
        </Link>
      </h2>
      <h2>
        <Link href={`products/${productId}`}>Product {productId}</Link>
      </h2>
    </>

replace参数会替换当前的历史记录条目,而不是添加一个新的。

获取当前访问路径

const pathname = usePathname();
const isActive = pathname === link.href || (pathname.startswith(link.href) && link.href !== "/ )

...

<Link className={isActive ? "font-bold mr-4" : "text-blue-500 mr-4"}
href={link.href}
key={link.name}
>
{link.name}
</Link>

params 和 searchParams

  • params 获取的是 路由参数: [参数名]
  • searchParams 获取的是query参数:?name=xxx
"use client";
import Link from "next/link";
import { use } from "react";

export default function NewsArticle({
  params,
  searchParams,
}: {
  params: Promise<{ articleId: string }>;
  searchParams: Promise<{ lang?: "en" | "es" | "fr" }>;
}) {
  const { articleId } = use(params);
  const { lang = "en" } = use(searchParams);

  return (
    <div>
      <h1>News Article #{articleId}</h1>
      <p>Reading in: {lang.toUpperCase()}</p>

      {/* Language switcher */}
      <div className="language-options">
        <Link href={`/articles/${articleId}?lang=en`}>English</Link> |
        <Link href={`/articles/${articleId}?lang=es`}>Español</Link> |
        <Link href={`/articles/${articleId}?lang=fr`}>Français</Link>
      </div>
    </div>
  );
}

导航页面

"use client"
...
const router = useRouter()
router.push("/")

layout.tsx、template.tsx、page.tsx渲染关系