Next.js 的 routing 跟一般常见的 react
+ react-route-dom
的组合不太一样,是采用 file-based routing。在 Next.js 中最基本的单位是 page,一个 page 就是一个 react component,component 会被放置在 pages
的资料夹中,而其档案名称将会决定路由的名称。
在 Next.js 可以分为三种的 routing 方式,分别为:
catch all routes 属於 dynamic routes 的一种
首先是 static routes,举一个例子,像是我们在前一天使用 create-next-app
产生的专案中,里面有个 pages/index.tsx
,所以在浏览网页时使用的路径就会是 /
。再举另一个例子,如果有个档案是被放置在 pages/post.tsx
,而路径就会是 /post
;如果是 page/post/index.tsx
跟前面一样的意思,同样路径会是 /post
。
这是最基本定义 page 的方式,用资料夹层级的方式来决定 url 会长什麽样子,使用这种方式就不用像是 react-router-dom
需要在档案中定义路由,还可以让整体的程序码更乾净。
要注意的是,如果是 component 是一个 page,则它必须用
default export
而不是named export
。
export default function Home({ post }: HomeProps) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
当读者看到这边,肯定会疑惑像是 /post/<post-id>
这种 url 该怎麽定义呢? <post-id>
是动态的,每新增一篇贴文难道就要新增一个 component 吗?这样的设计未免太不人性化了吧!
Next.js 当然有相对应的解决方案,动态的 url 可以用 dynamic routes 来处理。这也跟 file-based routing 有关系,因为就是透过档案名称的定义方式来实现 dynamic routes,以 /post/<post-id>
这个 url 来说,可以用 [postId].tsx
定义一个 page 的名称来达成 dynamic routes。
以下是 /pages/post/[postId].tsx
的范例程序:
import { useRouter } from "next/router";
const Post = () => {
const router = useRouter();
const { postId } = router.query;
return <p>Post: {postId}</p>;
};
export default Post;
在预设的情况下,像是 /post/123
或是 /post/abc
皆可以满足 /pages/post/[postId].tsx
,而 123
跟 abc
就会被当作是 url 的 query string,被并入到 router.query
里面。
举例来说, /post/123
并入到 router.query
的物件如下:
{
postId: 123;
}
如果在 url 加上一个 query string,例如 /post/123?hello=world
,则 router.query
这个物件就会长得像这个样子:
{ postId: 123, hello: 'world' }
而这边要特别注意的是,假设 query string 跟路由的名称一样,例如 /post/123?postId=abc
,则 abc
会被 123
盖过去,最终得到的结果如下:
{
postId: 123;
}
多层级的路由一样,也是透过 file-based routing 的方式定义,例如以 /post/123/abc
来说,我们就可以定义 /pages/post/<postId>/<commentId>.tsx
来匹配这个路由,在这个层级,也可以拿到前几个层级的参数,会被统一合并到 router.query
中:
{ postId: 123, commentId: 'abc' }
有时候在设计 url 时会遇到一种情况,并必须要在每一个层级都有代表的 component,虽然这样很弹性,但是就要花费比较多心思撰写更多的 component。
同样用上方 post 的例子来说,有时候会希望 post 的 url 能够以「年月日」来设计,所以一篇 post 的设 url 会设计成这个样子 /pages/post/<year>/<month>/<day>
,接下来读者可能会头痛了,难道要定义多层级的资料夹吗?而且最後可能还只有一个 /pages/.../day.tsx
,这样感觉挺麻烦的。
pages/
└── posts/
└── [year]/
└── [month]/
└── [day].js
这个问题 Next.js 也有相对应的解决方案,可以使用官方称作为「catch all routes」的定义方式,一次拿到所有层级的参数。
以上方的例子,只要定义 /pages/[...date].tsx
就可以匹配「年月日」的参数,而且甚至可以无限地加上新的参数,例如颗粒度想要细到小时、分钟、秒,都是可以的,因为 [...date]
的资料最後会以阵列被储存在 router.query
中,例如 /post/2021/12/31
会拿到以下这个物件:
{
date: [2021, 12, 31];
}
而再把 post 的颗粒度在切得更小的话,因为可能一天不止一篇贴文,像是 /post/2021/12/31/12/30/00
,最终就可以拿到这样子的物件:
{
date: [2021, 12, 31, 12, 30, 00];
}
Next.js 是一个很棒的框架,提供了完善的 file-based routing,可以用三种不同的 pattern 定义路由的规则。但是,在使用 router.query
时要注意「第一次 render 时拿不到值」的问题,因为 Next.js 有 Automatic Static Optimization 的机制,在第一个阶段 (第一次渲染) 会先执行 pre-rendering 产生静态的 HTML,这时候 router.query
会是空的 {}
,在第二个阶段 (第二次渲染) 时才能够从 router.query
中拿到值。
以下方这个范例来说,我们可以尝试在 pages/post/[postId].tsx
中简单地用 console.log
检查 postId
是否有值。
import { useRouter } from "next/router";
const Post = () => {
const router = useRouter();
const { postId } = router.query;
console.log(postId);
return <p>Post id: {postId}</p>;
};
export default Post;
从结果上来看, postId
没办法在第一阶段时就拿到值,如果想要操作 postId
就要特别小心这个问题。
在 Next.js 官方的 GitHub 上也有些人在讨论这个问题,在看过大家的讨论後,笔者整理了三种可行的解法,我们同样用上从 postId
的情境。
判断 postId
有没有值
const { postId } = router.query;
if (!postId) {
return <Loading />;
}
使用 isReady
判断是否能从 useRouter()
中取值 (官方不推荐将 isReady
使用在 conditionally rendering,只能被用於 useEffect
中)
const { query, isReady } = useRouter();
if (!isReady) {
return <Loading />;
}
从另一个参数 asPath
中用 regex
取值
const { asPath } = useRouter();
const { postId } = asPath.match(/\/post\/(?<postId>.*)/);
前两种方式是判断是否能够从 router.query
取得值,而第三种方式则是不用 Next.js 提方的方式,改用自己写 regex 的方法取值,但如果非必要得在第一次渲染时就取得值,否则不推荐使用第三种方式。
File-based routing | Code-based routing |
---|---|
Next.js | react + react-router-dom |
不需要在程序中定义 routing | 需要在程序中设定 <Router> 、 <Switch> ... |
直觉地用档案阶层定义 routing | 档案放的位置不必按照规范,可能 Route 会出现在意料之外的地方 |
使用 <Link> 作为 routing 的 component |
使用 <Link> 作为 routing 的 component |
<<: D-11, Ruby 正规表达式(三) 字符 && Regular Expression Matching
在回答问题前,我们可以先了解他们是做什麽用的,为什麽总是拿来被比较? 这里要先回忆一个观念: JS里...
後台 管理员能在後台页面查询用户购买纪录及明细 第一次进入此页面时无参数,在表单填入以下资讯後返回结...
在 JavaScript 中,物件型别是利用传参考的方式来传递它的值, 因此当我们要复制出独立的物件...
https://wolkesau.medium.com/java-语言-8e8158d75b5d J...
美国政府在2014年为了加强公部门的资安防护能力,颁布了NIST (NIST Cybersecuri...