在了解了 Next.js 的三种路由方式後,接下来就让我们来聊聊怎麽在 component 切换页面吧!
在 Next.js 中切换页面是用 client-side routing 的方式,其体验会很像是 SPA (Single Page Application),而不是像传统的 SSR 在切换页面时画面都会有重新载入的感觉。
Next.js 提供了 <Link />
这个 component,让我们可以在 Next.js 做到像是 SPA 的体验。还记得 Next.js 有三种路由的方式,分别为 static routes、dynamic routes、catch all routes,对於这三种不同的路由方式,在定义 <Link />
都大同小异。
import Link from "next/link";
function Home() {
return (
<ul>
<li>
<Link href="/post">
<a>切换至 pages/post/index.tsx</a>
</Link>
</li>
<li>
<Link href="/post/123">
<a>切换至 pages/post/[postId].tsx</a>
</Link>
</li>
<li>
<Link href="/post/2021/12/31">
<a>切换至 pages/post/[...date].tsx</a>
</Link>
</li>
</ul>
);
}
export default Home;
以上方的范例来说, <Link />
传入的 href
都是对应一个 page 的 url:
/post
对应的是 pages/post.tsx
或 pages/post/index.tsx
/post/123
对应的是 pages/post/[postId].tsx
/post/2021/12/31
对应的是 pages/post/[...date].tsx
href
href
不仅仅可以传入 url 字串也能够接受物件形式的 url 物件,我们透过 TypeScript 的型别定义来看看 href
可以传入的数值有哪些:
type Url = string | UrlObject;
interface UrlObject {
auth?: string | null | undefined;
hash?: string | null | undefined;
host?: string | null | undefined;
hostname?: string | null | undefined;
href?: string | null | undefined;
pathname?: string | null | undefined;
protocol?: string | null | undefined;
search?: string | null | undefined;
slashes?: boolean | null | undefined;
port?: string | number | null | undefined;
query?: string | null | ParsedUrlQueryInput | undefined;
}
从 Next.js 的型别定义中,我们可以看到 UrlObject
接受的参数非常多种,能够用非常弹性的方式定义路由的 url。
我们用上面比较简单来的例子改写成 UrlObject
:
<li>
<Link
href={{
pathname: "/post/[postId]",
query: { postId: "123" },
}}
>
<a>切换至 pages/post/[postId].tsx</a>
</Link>
</li>
<li>
<Link
href={{
pathname: "/post/[...date]",
query: { date: ["2021", "12", "31"] },
}}
>
<a>切换至 pages/post/[...date].tsx</a>
</Link>
</li>
可藉由 pathname
以相似 page 的方式传入 url,并在 query
传入可以从 useRouter()
中拿到的值:
/post/[postId]
对应的是 pages/post/[postId].tsx
/post/[...date]
对应的是 pages/post/[...date].tsx
href
既然 href
都能够接受 string
或 UrlObject
两种定义路由的方式,当然也能够接受动态生成 url 的方式。
例如贴文列表里面包含了许多贴文的连结,因此可以用 map
来生成许多的 <Link />
:
import Link from "next/link";
const Posts = () => {
const posts = [{ id: "1" }, { id: "2" }, { id: "3" }];
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/post/${post.id}`}>
<a>Post id: {post.id}</a>
</Link>
</li>
))}
</ul>
);
};
export default Posts;
但有一点必须要注意的是, <Link />
这个 component 只能接受里面有一个 child node,而且 child node 只能是 string
或是 <a>
,如上方的范例,假设把 <a>
拿掉以後 Next.js 便会报错。
因为对於 react 来说, Post id: {post.id}
是会分成两个 node,所以 Next.js 便无法顺利的编译。
在许多情况下 <Link />
已经够用,但是也许有些使用情境,我们需要延迟切换页面的事件,例如在点击按钮时想要触发 google analytics 的事件後,再触发换页。
所以,我们就可以用 useRouter
中的 router.push
来帮助我们达成这件事:
import { useRouter } from "next/router";
import ga from "../lib/ga";
const Posts = () => {
const posts = [{ id: "1" }, { id: "2" }, { id: "3" }];
const router = useRouter();
const handleRouteChange = (post_url: string) => {
ga.event({ action: "click_post_link", params: { post_url } });
router.push(post_url);
};
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<button onClick={() => handleRouteChange(`/post/${post.id}`)}>
<a>Post id: {post.id}</a>
</button>
</li>
))}
</ul>
);
};
export default Posts;
Shallow routing 是一种用於同一个 page 的路由,你能够改变 url 上的 query string,但是不执行 getServerSideProps
、 getStaticProps
与 getInitialProps
里面的程序,此外还会保留 page 中的状态。
要使用 shallow routing 需要用到 useRouter()
,在 router.push
的第三个参数加上 { shallow: true }
:
router.push(`/?counter=123`, undefined, { shallow: true });
以下是一个简单的范例,点击「add counter」的按钮後,会新增一个随机的数字,同时修改 url 上的 counter - query string,而 useEffect
会监听 router.query.counter
,在改变时储存到 counters
状态中。
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
// 目前 url 为 '/'
function Page() {
const router = useRouter();
const [counters, setCounters] = useState<number[]>([]);
const handleClick = () => {
const counter = Math.round(Math.random() * 100);
router.push(`/?counter=${counter}`, undefined, { shallow: true });
};
useEffect(() => {
if (router.query.counter) {
setCounters((prev) => [
...prev,
parseInt(router.query.counter as string),
]);
}
}, [router.query.counter]);
return (
<div>
<ul>
{counters.map((counter) => (
<li key={counter}>{counter}</li>
))}
</ul>
<button onClick={handleClick}>add counter</button>
</div>
);
}
export default Page;
Shallow routing 有一个限制是「只能在同一个 url 上切换」,像是以上方的范例来说,我们在在 url 上加上一段 query string — /?counter=${counter}
,这样的操作是合法的。以下示范一个不合法的操作:
router.push(`/products?counter=${counter}`, undefined, { shallow: true });
这个操作会将目前的页面转移到 /product
,等於 { shallow: true }
变成是一段没有用的参数。
>>: Day20:[排序演算法]Selection Sort - 选择排序法
今天突然整个不知道要写什麽 @@ 一定是礼拜六要上课的关系 ## 今天呢 就来讲讲有关於 Rust ...
理解js单执行绪&非同步运行机制 由於js为单执行绪,也就是一次只处理一件事情并依序执行,但...
Tip 2:随机梯度下降法(Stochastic Gradient Descent) 提升训练速度 ...
先说明一下 我用轻前端 Vue 的目的,不是把整个网站都改用轻前端,而是为了把复杂的 js 取值、给...
之前一直用的E5 2680v3打了鸡血,全核3.3G,打游戏不太好使毕竟目前的游戏大部分吃的单核高主...