AppRouter路由

NextJS的路由设计十分特别,它采用一种基于文件系统的路由声明方式。在大约20年前流行的PHP等网站开发技术中,曾经并不存在“路由”这一概念,浏览器访问的路径就是PHP文件相对于网站根目录的文件系统相对路径,20年后NextJS回归了这一大道至简的路由方式(不过其底层原理完全不同)。这篇笔记我们学习NextJS中的路由系统。

定义文件路由结构

在NextJS的早期版本中,文件路由使用Page Router结构,但最新的NextJS的13.4版本将路由系统改为了App Router,路由相关的API发生了较大的变化。新版本的NextJS中,路由的根目录是app文件夹(如果创建工程时采用src目录,app文件夹位于src内;如果创建工程时不采用src目录,app文件夹位于工程根目录)。假如我们有以下目录结构:

|_app
  |_page.jsx
  |_dashboard
    |_page.jsx

根据此目录结构,NextJS会生成如下2条路由:

/
/dashboard

如果我们嵌套多层目录结构,也会对应生成多层的嵌套路由。也就是说,NextJS中app为路由根路径,app下的目录结构对应路由;但要注意目录下必须存在page.jsx,其对应的目录才会被识别为路由。page.jsx是一个React组件,默认情况下NextJS会在服务端执行该组件,NextJS中如果需要使用客户端组件,需要在文件头声明"use client",有关混合渲染的内容将在后续章节介绍。

此外,如果目录名使用下划线开头,例如_components形式,该目录及其子目录会被忽略,不会被识别为路由定义。我们可以将不需要被识别为路由的React组件放置在这种路径中。下面例子代码中,我们在page.jsx中引入了_components目录中定义的组件。

import Header from "@/app/dashboard/_components/Header";

const Page = () => {
  return (
    <>
      <Header />
      <div>Hello, NextJS!</div>
    </>
  );
};

export default Page;

和很多其它框架类似,NextJS也对import路径进行了处理,@是工程根路径的别名。

修改页面头信息

通过前面学习我们知道page.jsx代表一个页面,HTML页面中通常包含标题、描述等头信息,NextJS的服务端组件中,约定使用metadata属性配置页面的头信息,能正确设置这些头信息无疑对SEO有极大帮助,下面是一个例子。

export const metadata = {
  title: "Welcome",
  description: "Welcome to my website!",
};

const Page = () => {
  return <div>Hello, NextJS!</div>;
};

export default Page;

实际上,我们只要在page.jsx中导出一个metadata属性即可。此外,实际开发中,我们可能还经常用到动态设置metadata的情况,在服务端组件中,这需要用到generateMetadata方法。

export const generateMetadata = async function () {
  return {
    title: "Welcome",
    description: "Welcome to my website!",
  };
};

const Page = () => {
  return <div>Hello, NextJS!</div>;
};

export default Page;

代码中的generateMetadata()函数内我们直接返回了相应的信息,不过实际上这里我们也可以进行更多操作,比如调用下接口查询等。此外要注意metadatagenerateMetadata不能同时使用。有关metadata属性的其它配置,我们可以参考文档中相关的章节。

Layout框架

Layout的概念在许多前端路由系统中都存在,它的作用类似一个“框”套住了路由的子组件,NextJS中我们可以在路由目录下创建layout.jsx作为该路径下所有子路由的框架,我们看一个简单的例子。

|_app
  |_dashboard
    |_layout.jsx
    |_page.jsx
    |_... // 其它子目录

layout.jsx

const Layout = ({children}) => {
    return <>
        <span>首页</span>
        <span>文章</span>
        <span>关于</span>
        <hr/>
        {children}
    </>;
};

export default Layout;

上面代码中,layout.jsx会作用于同级的路由和子级的路由。layout.jsx本身也是一个React组件,NextJS在它接收的props中传入了children属性,它是一个ReactNode对象,代表嵌入其中的路由组件。

路由分组

NextJS中路由分组使用小括号(分组名)作为目录名创建,下面是一个例子。

|_app
  |_(marketing)
    |_layout.jsx
    |_about
      |_page.jsx
    |_blog
      |_page.jsx
  |_(shop)
    |_layout.jsx
    |_account
      |_page.jsx

上面例子中,例如(marketing)并不会产生一个实际的路由,我们访问其中的页面还是使用类似/about的路径,但它会产生一个分组,在分组中我们可以单独定义layout.jsx框架,让不同的分组具有不同的Layout框架。

动态路由

NextJS中动态路由指类似/users/3的路由形式,其中3其实是一个参数,这种动态路由使用中括号[参数名]的形式指定。

|_app
  |_users
    |_[id]
      |_page.jsx

page.jsx

const Page = ({ params }) => {
  return <>{params.id}</>;
};

export default Page;

NextJS会将动态路由参数以params属性的形式传入组件的props中。

除此之外动态路由也支持多级,多级需要以[...参数数组名]的形式指定,下面是一个例子。

|_app
  |_users
    |_[...slug]
      |_page.jsx

page.jsx

const Page = ({ params }) => {
  return (
    <>
      {params.slug.map((item, index) => {
        return <div key={index}>{item}</div>;
      })}
    </>
  );
};

export default Page;

此时如果访问/users/a/b/cparams.slug中的内容即为数组['a', 'b', 'c'];如果访问/users,则无法匹配[...slug]内的页面。

使用Link组件和路由API

Link组件

<Link>是路由系统提供的组件,用于链接跳转,它会在浏览器中渲染为<a>标签。

import Link from "next/link";

const Page = () => {
  return (
    <>
      <Link href={"/dashboard"}>Dashboard</Link>
    </>
  );
};

export default Page;

上面例子代码中创建了一个跳转到/dashboard页面的链接。注意在NextJS工程中我们应该尽可能是使用<Link>而不是<a>,这是因为<Link>支持prefetch预加载、缓存等高级特性,而<a>标签则会被浏览器粗暴的理解为就是重新向服务器请求一个新的页面,使用<Link>而不是<a>有助于NextJS对我们网站响应速度进行优化。

路由相关Hooks函数

页面跳转

除了<Link>组件我们也可以使用NextJS提供的Hooks函数来访问和操作路由。

"use client";

import { useRouter } from "next/navigation";

const Page = () => {
  const router = useRouter();

  return (
    <>
      <button
        onClick={() => {
          router.push("/dashboard");
        }}
      >
        Click Me!
      </button>
    </>
  );
};

export default Page;

上面例子中我们使用了useRouter来操作路由,点击按钮时跳转到/dashboard页面。注意这里代码中包含了操作页面跳转的逻辑,我们很容猜到它底层是基于HistoryAPI来实现的,存在客户端操作,因此该React组件需要声明为客户端组件。有关混合渲染的内容将在后续章节详细介绍。

获取GET参数

获取GET参数可以使用useSearchParams()函数,注意使用该Hooks的组件也必须是客户端组件。

"use client";

import { useSearchParams } from "next/navigation";

const Page = () => {
  const searchParams = useSearchParams();
  const id = searchParams.get("id");
  const username = searchParams.get("username");

  return (
    <>
      <div>{id}</div>
      <div>{username}</div>
    </>
  );
};

export default Page;

代码中我们读取了GET参数中的idusername

获取请求路径

我们可以使用usePathname获取请求路径,这也是一个客户端组件的Hooks函数。

"use client";

import { usePathname } from "next/navigation";

const Page = () => {
  const pathname = usePathname();

  return (
    <>
      <div>{pathname}</div>
    </>
  );
};

export default Page;

代码中,假如我们访问了地址http://xxx/dashboard/a/b/c,函数usePathname()会返回/dashboard/a/b/c

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap