iT邦幫忙

2025 iThome 鐵人賽

DAY 18
0
Modern Web

從 React 學 Next.js:不只要會用,還要真的懂系列 第 18

【Day 18】React vs Next.js 內部連結的使用方式 - <Link>

  • 分享至 

  • xImage
  •  

昨天我們介紹了如何使用 layout 來建立共用的頁面架構,今天要接著來看另一個與路由系統密切相關的重要功能,那就是「內部連結(<Link>)」的使用方式。

在開發網頁時,說到「連結」通常會直覺想到使用<a>標籤。但如果是使用 React 或 Next.js 這類具備「客戶端路由系統(client-side routing)」的框架,就不能單純使用原生的<a>標籤來實作頁面跳轉,否則會導致整個頁面重新載入,導致失去 SPA 的優勢。

在這個情況下,如果我們需要維持在客戶端上渲染頁面的功能,我們就需要使用 React Router 或 Next.js 所提供的 <Link> 元件,讓頁面在不重整的情況下完成跳轉,並保留目前應用所採用的渲染模式。

用 React Router 實作內部連結

今天我們一樣從 React 的實作方式開始看內部連結的部分,如果想要在 React 專案中維持 CSR 的行為,需要使用 react-router-dom 提供的 <Link> 元件來取代原生 <a> 標籤。

用 Link 做為頁面內的連結

如果只是單純放一個內部頁面的連結,做法非常簡單,就是把 <Link> 引入,並透過 to 屬性指定要導向的路由路徑即可,例如下面這個例子:

import { Link } from "react-router-dom";
const AboutPage = () => {
  return (
    <div>
      <h1>About</h1>
      <p>This is the about page.</p>
      <Link to="/about/other-pages">Other Page</Link>
    </div>
  );
}

export default AboutPage

動態路由與物件語法

如果想要使用動態的路由,可以組上變數後,放到 Link 的 to 屬性上。

<Link to={`/about/${id}`}>go to about</Link>

也可以用物件語法的寫法,將動態的部分帶到 Link 上。

<Link
  to={{
    pathname: `/about/${id}`,
  }}
>
  go to about<
</Link>

這裡的物件寫法,還可以帶入 hash 或 query string。

<Link
  to={{
    pathname: "/new-page",
    search: "?type=1",
    hash: "#top",
  }}
>
  Shoes
</Link>

取得路由上的動態參數及 query string

在 React 上可以透過 Link 帶上一些動態參數和 query string 的部分,當然也就有方法可以從對應的頁面取得路由相關的內容。在 React 專案中,如果要取得路由上動態參數的部分,可以透過引入 react-router-dom 的 useParams 和 useSearchParams 來取得。useParams 可以取得路由上動態參數的部分,useSearchParams 則可以取得 query string 的部分。

在 React 中,如果我們透過 <Link> 建立了帶有動態參數或 query string 的內部連結,在實務上,也會有在對應的頁面中取得這些路由資訊的需求,這時候就可以透過引入 react-router-dom 的 useParams 和 useSearchParams 來取得。

- useParams: 用來取得路由中的動態參數(例如 /about/${id} 中的 id)。
- useSearchParams: 用來取得 query string(例如 /new-page?type=1 中的 type)。

接下來直接透過實際的使用例子來看看取得路由動態參數的方式。

import { useParams, useSearchParams } from 'react-router-dom';
const Detail = () => {
    // 取得params
    const params = useParams();
    // searchParams可以取得query, setSearchParams可以更新query
    const [searchParams, setSearchParams] = useSearchParams();
    // 取得 params
    console.log('params: ', params.id); // params:  10
    // 取得 query
    console.log('query: ', searchParams.get('type'));  //query:  a
  return (
    <div>
      <h1>Detail</h1>
    </div>
  );
}

export default Detail

當進入的網址為 http://localhost:3002/#/about/10?type=a 時,因為 10 的部分是動態參數的部分,所以透過 useParams 會取得 10,為 query string 部分的 type 則可以透過 useSearchParams 取得,在這個情境下會取得 a。

到這裡為止都只提到了 React 中的內部連結用法,接下來當然就是要看看我們的男主角 Next.js 的用法囉。

Next.js 中的內部連結用法

在 Next.js 中,不論是使用 Page Router 還是 App Router,一樣都是使用 <Link > 來達到內部連結的效果。雖然在 <Link > 的使用上,Page Router 和 App Router 的差異不大,但實際上還是有些許差異。

接下來除了看 Next.js 中如何使用內部連結之外,也會來看一下 Page Router 和 App Router 在使用 <Link> 時,相同和相異之處的部分有哪些。

Page Router 和 App Router 相同的 <Link> 使用部分

和 React 一樣使用 <Link>

在 Next.js 中的 Page Router 和 App Router 要設定內部連結時,一樣都是使用 <Link>,只是這個 Link 不是從 react-router-dom 引入,而是從 next/link 引入。還有一個不同的地方是,使用 Link 時,帶入連結網址使用的屬性不是 to,改成使用 href。

import Link from "next/link";

const AboutA = () => {
  return (
    <div>
      <p>About A</p>
      <Link href="/about">
        go to about
      </Link>
    </div>
  );
};

export default AboutA;

動態路由及 query 的用法

在 Next.js 的 Page Router 和 App Router 中,如果要使用動態路由和加上 query string 的話,一樣也會使用相同的方式,把相關資訊帶到 <Link> 中。

可以透過 template 語法將變數部分組成要前往的路由路徑,再帶到 to 屬性上。

<Link href={`/about/${id}`}>go to about</Link>

也可以用物件的方式把相關資訊帶過去。

<Link href={{ pathname: '/about/[id]', query: { id: 1 } }}>
 go about
</Link>

Page Router 和 App Router 相異的 <Link> 使用部分

大多數的一些基本的 <Link> 用法,不論是在 Page Router 還是在 App Router 上都一樣,但還是有些微的差距。接下來也來看看差異的部分有哪些?

取得路由動態變數部分的用法

在這個部分,不論是 Page Router 還是 App Router,都會依照是 CSR 還是 SSR 或 SSG 的渲染模式,而用不同的方式去取得路由中動態變數和 query string 的部分。但是取得方式的寫法,會依照是 Page Router 或 App Router 有不同。

∙ CSR 模式
如果是使用 Page Router,在 CSR 模式下,可以引入 next/router 的 useRouter 來取得動態路由或 query string。

例如以下的寫法,當進入的網址為 http://localhost:3002/#/about/10?type=a 時,就可以透過 useRouter 取得 id 和 type 的資訊。

import { useRouter } from "next/router";

const Detail = () => {
  const router = useRouter();
  console.log(router.query); // {type: 'a', id: '10'}
  return (
    <div>
      <h1>Detail</h1>
    </div>
  );
};

export default Detail;

如果是 App Router,在 CSR 模式下,則要透過引入 next/navigationuseParamsuseSearchParams 來取得路由中動態的部分及 query string。

當進入的網址為 http://localhost:3002/#/about/10?type=a

用以下的寫法,就能從 params 取得動態路由的內容,從 searchParams 則可以取得 query string 的部分。

"use client";

import { useParams, useSearchParams } from "next/navigation";

const Detail = () => {
  const params = useParams();
  const searchParams = useSearchParams();

  const id = params.id;
  const type = searchParams.get("type");

  console.log(id, type); // '10', 'a'
  return (
    <div>
      <h1>Detail</h1>
    </div>
  );
};

export default Detail;

∙ SSR 模式
在 SSR 模式沒辦法使用 hook,如果要在 Page Router 下取得網址上的動態參數和 query 的話,就需要使用 getServerSideProps

export async function getServerSideProps({ params, query }) {
  return {
    props: {
      id: params.id,
      type: query.type || null,
    },
  };
}

const Detail = ({ id, type }) => {
  return (
    <div>
      <p>Post ID: {id}</p>
      <p>Query: {type}</p>
    </div>
  );
};

export default Detail;

在 App Router 下,在取得方法上就相對地簡單,只要從 page 檔案中的 fucntion 解構出 params 和 searchParams,在伺服器上就可以取得相對應的值。但要注意的是這裡的 params 和 searchParams 都是 Promise,所以想要取得值的內容,需要用 await

const Detail = async ({ params, searchParams }) => {
  const { id } = await params
  const { type } = await searchParams

  console.log(id, type); // '10', 'a'
  return (
    <div>
      <h1>Detail</h1>
    </div>
  );
};

export default Detail;

∙ SSG 模式下
在 SSG 這種渲染模式中,情況會稍微有點不一樣,會需要讓動態路由(例如:/detail/[id])變成是可預期的動態路由。因為在 SSG 模式下,會在 build 階段就產出完整 HTML,所以在這個階段,Next.js 必須要知道有哪些動態路由參數(例如:/detail/[id] 內有哪些 id),用來預先生成對應的頁面。因此我們會需要透過一些方式主動告訴 Next.js 哪些路徑要被產出成靜態頁。

也是因為如此,當有完全無法預期的動態內容時,就無法使用 SSG。例如使用者動態新增的資料(如會員 ID、留言 ID)、或者查詢參數(query string)這類在 build 階段無法得知的資訊,都無法提前對應生成對應的 HTML。

使用 Page Router 時,如果要在 SSG 這種渲染模式中,動態路由變成可預期的動態路由,需要搭配 getStaticPropsgetStaticPaths 一起使用,這樣才能讓 Next.js 知道可能會有哪些動態資料。

export async function getStaticPaths() {
  return {
    //  這裡會需要放入可預期的動態資料
    paths: [{ params: { id: "1" } }],
    fallback: false,
  };
}
export async function getStaticProps({ params }) {
  return {
    props: {
      id: params.id,
    },
  };
}

const Detail = ({ id }) => {
  return (
    <div>
      Post ID: {id}
    </div>
  );
};

export default Detail;

如果路由中的動態參數(如: id)無法預先得知,這時可以改用 ISR,搭配 fallback: 'blocking' 的方式來達成靜態頁面的延遲生成與快取。

當 fallback 設為 'blocking' 時,若使用者輸入的 id 不在 getStaticPaths 所定義的列表中,Next.js 會在伺服器端同步產生對應的 HTML 頁面,產生完成後才回傳給使用者。這個頁面會被快取起來,讓下一次訪問相同路徑的使用者能直接獲得靜態頁面,不需要重新生成。

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }],
    // 改成 blocking
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  return {
    props: {
      id: params.id,
    },
    revalidate: 60, // 60 秒後允許再重新生成新版本
  };
}

在 App Router 和 Page Router 使用 SSG 渲染模式時一樣,需要另外搭配一個 function 去建立一些可預期的動態參數內容,但是 App Router 要使用的 function 是 generateStaticParams。取出來的方式和 SSR 的時候一樣,是透過解構 page 中的 function 取得。

export async function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }];
}

export default async function Detail({ params }) {
  const { id } = await params;

  console.log(id);

  return (
    <div>
      <h1>Detail</h1>
    </div>
  );
}

另外,需要注意的部分是如果想要維持 SSG 這個渲染模式,不可以使用到 searchParams 的話,如果使用了解構出來的 searchParams 就會被視為是動態渲染,也就無法維持 SSG,在 build 的時候也就不會產生出 HTML。

Link 的 prefetch 機制

這裡也透過觀察 Link 的 prefetch 機制,來了解 Page Router 和 App Router 在這部分上的差異。

不論 Page Router 還是 Page Router 在頁面上如果有使用 <Link> 的話,都會做一個 prefetch 的動作,但是在進行 prefetch 的時機點和 prefetch 的內容上,有些許差異。

在 Page Router 中,當滑鼠滑過 Link,就會先加載這個 Link 對應頁面的 JavaScript chunk。
https://i.imgur.com/6st1t6H.gif

在 App Router 中,則是只要頁面有出現 Link 就會預先加載對應頁面的 RSC Payload。
https://ithelp.ithome.com.tw/upload/images/20250913/20130914tZYqiYn6Jp.png

不論是使用 React 還是 Next.js,善用 <Link> 都是維持單頁應用(SPA) 的關鍵之一。尤其在 Next.js 的 App Router 中,Link 的功用不只是在於導航功能,更牽涉到 RSC payload 的 prefetch,能讓頁面的效能變得更好。

今天 Link 的內容就到這裡告一個段落,明天我們會接著來看搭配 router 客製化定義錯誤頁面的部分。


上一篇
【Day 17】設定頁面大框架 Layout - Page Router 到 App Router 的用法演進
下一篇
【Day 19】React vs Next.js 設定客製化錯誤頁面
系列文
從 React 學 Next.js:不只要會用,還要真的懂19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言