前言
说好不鸽,第二篇来的晚了一些,在了解完nextjs的基本情况后,接下来就进入正式开发阶段啦,这一部分以app router为基础,如果有pages router的小伙伴建议可以尝试下app router,内容比较多,以官网为主,加上本人的理解,废话说完了,开始正题~
ps:本节依旧假设域名是xiaoqian.best
上一篇文章里说了,nextjs中页面的路由就是根据app的目录自动生成的,所以app文件夹中的架构就等于项目中的路由结构,app文件夹中总共包含以下约定文件(这里说的文件指的是约定俗成有特定文件名并有特殊作用的文件,除此之外,在app中可以按需创建其他文件),这些文件可以是js,jsx和tsx,官方建议使用tsx。
接下来就一个个开始讲吧~
首先是熟悉的老朋友,layout,我们在上一篇有过基础的了解,首先,在根目录下它是必须存在的,根目录下的layout被用来定义以及
,一些全局性的配置和需要全局生效的第三方插件,比如一些埋点插件,组件库配置等也需要在layout中完成
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
// 可以设置字体样式
const inter = Inter({ subsets: ["latin"] });
// 可以设置metadata
export const metadata: Metadata = {
title: "Hello! xiaoqian",
description: "Hi! xiaoqian",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
参数
app/blog/[blogId]/layout.tsx
里,blogId
在路由中可以渲染成任意的值,比如访问xiaoqian.best/blog/XXXXX
时,layout中params={blogId:XXXXX}
,动态路由下次会细讲。注意事项:
要使用searchParams怎么办呢,别慌,客户端渲染模式下有个 useSearchParams可以使用,服务端渲染嘛。。。就建议别搞这些复杂的东西了(Pages Router 可以接收searchParams,有兴趣可以看看)
pathname
。这是因为layout默认是服务器组件,并且在客户端导航期间不会重新渲染,这可能导致pathname
在导航之间变得过时。为了防止过时,Next.js 需要重新获取所有router,从而失去缓存的优势并增加导航上的 RSC 有效负载大小相反,可以将依赖于
pathname
的逻辑提取到客户端组件中,并将其导入到layout中。由于客户端组件会在导航期间重新渲染(但不会重新获取),可以使用 Next.js hooks(例如 usePathname)来访问当前pathname
并防止过时。最好的方式还是用动态路由吧~
有人就会有疑问了,既然有和
了,那我能不能把
和
也写到layout中去咧,答案是最好不要,看到代码中的metadata声明了吗,和
写在那里会更好,因为metadata会自动处理高级要求,例如流式传输和删除重复 元素。具体metadata怎么用,能设置什么属性,展开来说太多了,另开一篇文章讲(不会鸽不会鸽)。
那我们可以创建多个根layout么,比如我这个项目里有聊天和博客两个完全不一样的模块,每个模块配置,样式都不一样,此时可以使用路由组(route group,水也深,下次下次),比如app/(marketing)/layout.js
和 app/(shop)/layout.js
。
注意事项
如果代码里面使用了window.xxx的方法(比如window.localstorage),加载时可能会出现window is not definded的报错,这个时候需要对layout进行如下一些操作
原理:layout组件默认是服务端渲染,页面还没渲染出来就开始执行方法,window对象还没产生,需要通过useEffect判断初次渲染是否完成(useEffect的出现,等于这个页面就只能用客户端渲染,第一行加上'use client'
声明)
'use client'
import { useEffect, useState } from "react";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const [showing,setShowing] = useState(false)
// 确保初始化渲染完成后再加载内容
useEffect(()=>{
setShowing(true)
},[])
// 之前可以直接这样写声明,最近升级14.2之后,这样写会报错
// if(showing) return null
return (
<html lang="en">
<body className={inter.className}>
{showing && children}
</body>
</html>
);
}
page比较简单,就是当前路径下的视窗,app/page.tsx里写的内容,就是xiaoqian.best里展示的内容,app/hahaha/page.tsx里写的内容,就会展示在xiaoqian.best/hahaha这个路径里。
参数
/shop?a=1&b=2 =>. { a: ‘1’, b: ‘2’ }
/shop?a=1&a=2 => { a: [‘1’, ‘2’] }
loading文件可以创建基于 Suspense 的即时加载状态,这个文件默认是服务端渲染,但也可以用use client
来标记成客户端渲染,这个文件没有任何参数
也就是说,我们可以在加载内容时显示服务器的即时加载状态(也就是还没渲染完成之前的占位样式,比如骨架图,loading样式等)。渲染完成后,新内容会自动换入。
在同级文件夹中,loading会被嵌入到layout中,自动将 page文件和下面的所有子文件包装在 边界中
// app/test/loading.tsx
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingSkeleton />
}
解析成React大概是下面这个意思
<Layout>
<Suspense fallback={ /> }>
<Page />
Suspense>
Layout>
画外音:App Router 支持使用 Suspense 对 Node.js 和 Edge 运行时进行流式传输
是不是有很多小伙伴对Suspense没有一个深入的了解,俺也一样,所以loading.js在我项目中目前使用不是很多,但是查看资料后发现这还是一个很有用的,简化业务代码的很好用的Nextjs功能,下次使用的时候再细说
一句话解释:404页面,默认情况下长下面这样,你也可以自己设计,在app/not-found.tsx里面定义的是全局的404页面,当然了,理论上app文件夹中的每个子文件夹里都可以自定义一个404页面。
除了提供自定义 UI 之外,Next.js 还将针对流式响应返回 200 HTTP 状态代码,针对非流式响应返回 404
顾名思义,一旦系统出现了某些问题,导致渲染报错,error就会跑出来了,它会捕获服务器组件和客户端组件中发生的意外错误,显示它的UI
参数
error文件是嵌在layout组件中的,所以没有办法捕获layout的错误,那怎么办,请往下看
这家伙,自带global光环,只能在根目录中存放,也就是app/global-error.tsx
,这就解决了layout错误的捕获,因为它会替换掉根目录中的layout,因此必须定义自己的 和 标签
route文件用来创建对应路由下的自定义请求,支持以下 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。,具体的还没有玩过,使用之后再细说
** 参数**
template与layout类似,它会把layout和page都嵌套进去。与跨路线持续存在并维护状态的layout不同,模板被赋予一个唯一的键,也就是说子路由每次加载时,layout会保持原有状态,但template状态会被重置掉。
为啥咧,因为当用户在共享模板的路由之间导航时,组件会创建新的新实例,重新创建 DOM 元素,因此不会保留状态,并且会重新同步效果
这个特性比较不常见,但有些场景下还是有用的,比如说:
** 参数 **
这个是平行路由的fallback页面,等讲路由的时候再详细讨论