You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// app/layout.jsimport'./globals.css'import{Inter}from'next/font/google'constinter=Inter({subsets: ['latin']})exportconstmetadata={title: 'Create Next App',description: 'Generated by create next app',}exportdefaultfunctionRootLayout({ children }){return(<htmllang="en"><bodyclassName={inter.className}>{children}</body></html>)}
其中:
app 目录必须包含根布局,也就是 app/layout.js 这个文件是必需的。
根布局必须包含 html 和 body标签,其他布局不能包含这些标签。如果你要更改这些标签,不推荐直接修改。
你可以使用路由组创建多个根布局。
默认根布局是服务端组件且不能设置为客户端组件。
4.4. 定义模板(Templates)
模板类似于布局,它也会传入每个子布局或者页面。但不会像布局那样维持状态。
模板在路由切换时会为每一个 children 创建一个实例。这就意味着当用户在共享一个模板的路由间跳转的时候,将会重新挂载组件实例,重新创建 DOM 元素,不保留状态。这听起来有点抽象,没有关系,我们先看看模板的写法,再写个 demo 你就明白了。
定义一个模板,你需要新建一个名为 template.js 的文件,该文件默认导出一个 React 组件,该组件接收一个 children prop。我们写个示例代码。
在 app目录下新建一个 template.js文件:
template.js 代码如下:
// app/template.jsexportdefaultfunctionTemplate({ children }){return<div>{children}</div>}
前言
路由(Router)是 Next.js 应用的重要组成部分。在 Next.js 中,路由决定了一个页面如何渲染或者一个请求该如何返回。
Next.js 有两套路由解决方案,之前的方案称之为“Pages Router”,目前的方案称之为“App Router”,两套方案目前是兼容的,都可以在 Next.js 中使用。
从 v13.4 起,App Router 已成为默认的路由方案,新的 Next.js 项目建议使用 App Router。
本篇我们会学习 App Router 下路由的定义方式和常见的文件约定。
1. 文件系统(file-system)
Next.js 的路由基于的是文件系统,也就是说,一个文件就可以是一个路由。举个例子,你在
pages
目录下创建一个index.js
文件,它会直接映射到/
路由地址:在
pages
目录下创建一个about.js
文件,它会直接映射到/about
路由地址:2. 从 Pages Router 到 App Router
现在你打开使用
create-next-app
创建的项目,你会发现默认并没有pages
这个目录。查看packages.json
中的 Next.js 版本,如果版本号大于13.4
,那就对了!Next.js 从 v13 起就使用了新的路由模式 —— App Router。之前的路由模式我们称之为“Pages Router”,为保持渐进式更新,依然存在。从 v13.4 起,App Router 正式进入稳定化阶段,App Router 功能更强、性能更好、代码组织更灵活,以后就让我们使用新的路由模式吧!
可是这俩到底有啥区别呢?Next.js 又为什么升级到 App Router 呢?知其然知其所以然,让我们简单追溯一下。以前我们声明一个路由,只用在
pages
目录下创建一个文件就可以了,以前的目录结构类似于:这种方式有一个弊端,那就是
pages
目录的所有 js 文件都会被当成路由文件,这就导致比如组件不能写在pages
目录下,这就不符合开发者的使用习惯。(当然 Pages Router 还有很多其他的问题,只不过目前我们介绍的内容还太少,为了不增加大家的理解成本,就不多说了)升级为新的 App Router 后,现在的目录结构类似于:
使用新的模式后,你会发现
app
下多了很多文件。这些文件的名字并不是我乱起的,而是 Next.js 约定的一些特殊文件。从这些文件的名称中你也可以了解文件实现的功能,比如布局(layout.js)、模板(template.js)、加载状态(loading.js)、错误处理(error.js)、404(not-found.js)等。简单的来说,App Router 制定了更加完善的规范,使代码更好被组织和管理。至于这些文件具体的功能和介绍,不要着急,本篇我们会慢慢展开。
3. 使用 Pages Router
当然你也可以继续使用 Pages Router,如果你想使用 Pages Router,只需要在
src
目录下创建一个pages
文件夹或者在根目录下创建一个pages
文件夹。其中的 JS 文件会被视为 Pages Router 进行处理。但是要注意,虽然两者可以共存,但 App Router 的优先级要高于 Pages Router。而且如果两者解析为同一个 URL,会导致构建错误。
注意:你在 Next.js 官方文档进行搜索的时候,左上角会有 App 和 Pages 选项,这对应的就是 App Router 和 Pages Router
因为两种路由模式的使用方式有很大不同,所以搜索的时候注意选择正确的的路由模式。
4. 使用 App Router
4.1. 定义路由(Routes)
现在让我们开始正式的学习 App Router 吧。
首先是定义路由,文件夹被用来定义路由。每个文件夹都代表一个对应到 URL 片段的路由片段。创建嵌套的路由,只需要创建嵌套的文件夹。举个例子,下图的
app/dashboard/settings
目录对应的路由地址就是/dashboard/settings
:4.2. 定义页面(Pages)
那如何保证这个路由可以被访问呢?你需要创建一个特殊的名为
page.js
的文件。至于为什么叫page.js
呢?除了page
有“页面”这个含义之外,你可以理解为这是一种约定或者规范。(如果你是 Next.js 的开发者,你也可以约定为index.js
甚至louis.js
!)在上图这个例子中:
app/page.js
对应路由/
app/dashboard/page.js
对应路由/dashboard
app/dashboard/settings/page.js
对应路由/dashboard/settings
analytics
目录下因为没有page.js
文件,所以没有对应的路由。这个文件可以被用于存放组件、样式表、图片或者其他文件。当然不止
.js
文件,Next.js 默认是支持 React、TypeScript 的,所以.js
、.jsx
、.tsx
都是可以的。那
page.js
的代码该如何写呢?最常见的是展示 UI,比如:访问
http://localhost:3000/
,效果如下:4.3. 定义布局(Layouts)
布局是指多个页面共享的 UI。在导航的时候,布局会保留状态、保持可交互性并且不会重新渲染,比如用来实现后台管理系统的侧边导航栏。
定义一个布局,你需要新建一个名为
layout.js
的文件,该文件默认导出一个 React 组件,该组件应接收一个children
prop,chidren
表示子布局(如果有的话)或者子页面。举个例子,我们新建目录和文件如下图所示:
相关代码如下:
当访问
/dashboard
的时候,效果如下:其中,
nav
来自于app/dashboard/layout.js
,Hello, Dashboard!
来自于app/dashboard/page.js
你可以发现:同一文件夹下如果有 layout.js 和 page.js,page 会作为 children 参数传入 layout。换句话说,layout 会包裹同层级的 page。
app/dashboard/settings/page.js
代码如下:当访问
/dashboard/settings
的时候,效果如下其中,
nav
来自于app/dashboard/layout.js
,Hello, Settings!
来自于app/dashboard/settings/page.js
你可以发现:布局是支持嵌套的,
app/dashboard/settings/page.js
会使用app/layout.js
和app/dashboard/layout.js
两个布局中的内容,不过因为我们没有在app/layout.js
写入可以展示的内容,所以图中没有体现出来。根布局(Root Layout)
布局支持嵌套,最顶层的布局我们称之为根布局(Root Layout),也就是
app/layout.js
。它会应用于所有的路由。除此之外,这个布局还有点特殊。使用
create-next-app
默认创建的layout.js
代码如下:其中:
app
目录必须包含根布局,也就是app/layout.js
这个文件是必需的。html
和body
标签,其他布局不能包含这些标签。如果你要更改这些标签,不推荐直接修改。4.4. 定义模板(Templates)
模板类似于布局,它也会传入每个子布局或者页面。但不会像布局那样维持状态。
模板在路由切换时会为每一个 children 创建一个实例。这就意味着当用户在共享一个模板的路由间跳转的时候,将会重新挂载组件实例,重新创建 DOM 元素,不保留状态。这听起来有点抽象,没有关系,我们先看看模板的写法,再写个 demo 你就明白了。
定义一个模板,你需要新建一个名为
template.js
的文件,该文件默认导出一个 React 组件,该组件接收一个children
prop。我们写个示例代码。在
app
目录下新建一个template.js
文件:template.js
代码如下:你会发现,这用法跟布局一模一样。它们最大的区别就是状态的保持。如果同一目录下既有
template.js
也有layout.js
,最后的输出效果如下:也就是说
layout
会包裹template
,template
又会包裹page
。某些情况下,模板会比布局更适合:
注:关于模板的适用场景,可以参考《Next.js v14 的模板(template.js)到底有啥用?》,对这两种情况都做了举例说明
布局 VS 模板
为了帮助大家更好的理解布局和模板,我们写一个 demo,展示下两者的特性。
项目目录如下:
其中
dashboard/layout.js
代码如下:dashboard/template.js
代码如下:dashboard/page.js
代码如下:dashboard/about/page.js
代码如下:dashboard/settings/page.js
代码如下:最终展示效果如下(为了方便区分,做了部分样式处理):
现在点击两个
Increment
按钮,会开始计数。随便点击下数字,然后再点击About
或者Settings
切换路由,你会发现,Layout 后的数字没有发生变化,Template 后的数字重置为 0。这就是所谓的状态保持。注:当然如果刷新页面,Layout 和 Template 后的数字肯定都重置为 0。
4.5. 定义加载界面(Loading UI)
现在我们已经了解了
page.js
、layout.js
、template.js
的功能,然而特殊文件还不止这些。App Router 提供了用于展示加载界面的loading.js
。这个功能的实现借助了 React 的
Suspense
API。关于 Suspense 的用法,可以查看 《React 之 Suspense》。它实现的效果就是当发生路由变化的时候,立刻展示 fallback UI,等加载完成后,展示数据。初次接触 Suspense 这个概念的时候,往往会有一个疑惑,那就是——“在哪里控制关闭 fallback UI 的呢?”
哪怕在 React 官网中,对背后的实现逻辑并无过多提及。但其实实现的逻辑很简单,简单的来说,ProfilePage 会 throw 一个数据加载的 promise,Suspense 会捕获这个 promise,追加一个 then 函数,then 函数中实现替换 fallback UI 。当数据加载完毕,promise 进入 resolve 状态,then 函数执行,于是更新替换 fallback UI。
了解了原理,那我们来看看如何写这个
loading.js
吧。dashboard
目录下我们新建一个loading.js
。loading.js
的代码如下:// app/dashboard/loading.js export default function DashboardLoading() { return <>Loading dashboard...\</> }
同级的
page.js
代码如下:不再需要其他的代码,loading 的效果就实现了,就是这么简单。其关键在于
page.js
导出了一个 async 函数。loading.js
的实现原理是将page.js
和下面的 children 用<Suspense>
包裹。因为page.js
导出一个 async 函数,Suspense 得以捕获数据加载的 promise,借此实现了 loading 组件的关闭。当然实现 loading 效果,不一定非导出一个 async 函数。也可以借助 React 的
use
函数。现在我们在dashboard
下新建一个about
目录,在其中新建page.js
文件。/dashboard/about/page.js
代码如下:如果你想针对
/dashboard/about
单独实现一个 loading 效果,那就在about
目录下再写一个loading.js
即可。如果同一文件夹既有
layout.js
又有template.js
又有loading.js
,那它们的层级关系是怎样呢?对于这些特殊文件的层级问题,直接一张图搞定:
4.6. 定义错误处理(Error Handling)
再讲讲特殊文件
error.js
。顾名思义,用来创建发生错误时的展示 UI。其实现借助了 React 的 Error Boundary 功能。简单来说,就是给 page.js 和 children 包了一层
ErrorBoundary
。我们写一个 demo 演示一下
error.js
的效果。dashboard
目录下新建一个error.js
,目录效果如下:dashboard/error.js
代码如下:为触发 Error 错误,同级
page.js
的代码如下:有时错误是暂时的,只需要重试就可以解决问题。所以 Next.js 会在
error.js
导出的组件中,传入reset
函数,帮助尝试从错误中恢复。该函数会触发重新渲染错误边界里的内容。如果成功,会替换展示重新渲染的内容。还记得上节讲过的层级问题吗?让我们回顾一下:
从这张图里你会发现一个问题:因为
Layout
和Template
在ErrorBoundary
外面,这说明错误边界不能捕获同级的layout.js
或者template.js
中的错误。如果你想捕获特定布局或者模板中的错误,那就需要在父级的error.js
里进行捕获。那问题来了,如果已经到了顶层,就比如根布局中的错误如何捕获呢?为了解决这个问题,Next.js 提供了
global-error.js
文件,使用它时,需要将其放在app
目录下。global-error.js
会包裹整个应用,而且当它触发的时候,它会替换掉根布局的内容。所以,global-error.js
中也要定义<html>
和<body>
标签。global-error.js
示例代码如下:注:
global-error.js
用来处理根布局和根模板中的错误,app/error.js
建议还是要写的4.7. 定义 404 页面
最后再讲一个特殊文件 ——
not-found.js
。顾名思义,当该路由不存在的时候展示的内容。Next.js 项目默认的 not-found 效果如下:
如果你要替换这个效果,只需要在
app
目录下新建一个not-found.js
,代码示例如下:not-found 的效果就会更改为:
关于
app/not-found.js
一定要说明一点的是,它只能由两种情况触发:所以
app/not-found.js
可以修改默认 404 页面的样式。但是,如果not-found.js
放到了任何子文件夹下,它只能由notFound
函数手动触发。比如这样:执行 notFound 函数时,会由最近的 not-found.js 来处理。但如果直接访问不存在的路由,则都是由
app/not-found.js
来处理。对应到实际开发,当我们请求一个用户的数据时或是请求一篇文章的数据时,如果该数据不存在,就可以直接丢出
notFound
函数,渲染自定义的not-found.js
界面。示例代码如下:注:后面我们还会讲到“路由组”这个概念,当
app/not-found.js
和路由组一起使用的时候,可能会出现问题。具体参考 《Next.js v14 如何为多个根布局自定义不同的 404 页面?竟然还有些麻烦!欢迎探讨》小结
恭喜你,完成了本节内容的学习!
这一节我们重点讲解了 Next.js 基于文件系统的路由解决方案 App Router,介绍了用于定义页面的
page.js
、定义布局的layout.js
、定义模板的template.js
、定义加载界面的loading.js
、定义错误处理的error.js
、定义 404 页面的not-found.js
。现在你再看 App Router 的这个目录结构:对此是不是有了更加深刻的理解呢?然而这还只有 Next.js 强大的路由功能的一小部分。下篇让我们继续学习。
参考链接
The text was updated successfully, but these errors were encountered: