為你的 React Router 包裝 Navigate

tags: React
category: Front-End
description: 為你的 React Router 包裝 Navigate
created_at: 2022/05/23 19:30:00

cover image


前言

這邊所指的 React Router 指的是 v6 的版本,在這個版本中多了一個叫做 navigate 的東西去幫助你做轉址之類的操作。

而為什麼已經有 useNavigate 這個 hook,還需要再做包裝呢?

因為他的定義像是這個樣子

declare function useNavigate(): NavigateFunction;

interface NavigateFunction {
  (
    to: To,
    options?: { replace?: boolean; state?: any }
  ): void;
  (delta: number): void;
}

簡單來說他就是支援兩種操作

  1. 給我 你要去哪裡(to)額外參數(options),然後幫你轉過去
  2. 只給我一個數字,(通常是用在上一頁 填 -1)

而這兩種操作雖然看起來很棒,嗯他們確實很棒,但是不夠我用啊!!,我需要路由參數的話怎麼辦? 我又不想要乖乖組字串

所以才自己想額外包裝一個使用的方式,增加自己開發的體驗。


定義需求

因為在 React Router 中,路由的用法大概長得像下面這樣(不論有無巢狀)。

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users/:id" element={<UserPage />} />
  <Route path="/other" element={<OtherPage />} />
</Routes>

因為我們關注的點在於那個 path,所以跟巢狀沒有太大的關係。

所以假設以目前官方提供的方式,我希望轉入 users/:id 這個頁面,我必須這樣做

import { useNavigate } from 'react-router-dom'
// ...
const navigate = useNavigate()
navigate('/users/1')
// ...

而那個數字如果是動態的我該怎麼辦呢? 可能只能採取下面的作法

const user = { id: 1 }  // <-- 假設我從其他地方來,例如 API
navigate('/users/' + user.id)  // <-- 原始的字串+法
navigate(`/users/${user.id}`)  // <-- ES6的模板字串

這時可能有些人會對 navigate 的使用方式有疑問,上面提到了還有第二個參數 options 裡面有個 { state: } 不是也可以帶資料嗎(?)

對,那個也可以帶資料,但是不會影響到網址上面,也就是如果你把網址貼給別人沒辦法重現一樣的行為 (?)

所以我希望可以做到這件事

const navigate = useParamNavigate()  // <-- 自己寫的 hook

// 希望可以藉由以下方式切換路由參數
navigate('/users/:id', { id: 1})
navigate('/users/:id', { id: 2})
navigate('/users/:id', { id: 3})

如果透過這樣的方式去轉址,不管前面的網址在複雜,你用起來都快樂很多。

假設我這邊用不到那個 options 的功能,所以我先不 forward 他,如果自己有需求再自己加。


開始包裝

包裝前當然你要有一個 React 專案,所以還沒有的自己建一下

$ npx create-react-app <project-name>
or
$ yarn create react-app <project-name>

首先你還必須安裝套件,基本上可以參考官方: https://reactrouter.com/docs/en/v6/getting-started/installation

但我這邊就貼比較常用的方式

$ npm install react-router-dom@6
or
$ yarn add react-router-dom@6

然後再次順便廣告一下,如果要快速幫你裝好像是 EslintPrettierTailwindcss 之類的環境,可以考慮我做的 cmd: https://github.com/LaiJunBin/lai-cmd


都安裝好之後終於可以開始了,首先建立一個檔案,假設我叫做 useParamNavigate.js

import 'core-js/features/string/match-all'  // <-- 如果加這行需要額外安裝套件,可參考下方說明
import { useNavigate } from 'react-router-dom'

const useParamNavigate = () => {
  const navigate = useNavigate()  // <-- 使用原來的 navigate

  return (path = '/', params = {}) => {  // <-- 這邊是要回傳回去給程式使用的 function
    for (const [k, v] of path.matchAll(/:([^/]+)/g)) {  // <-- 使用正規表達式抓出路由參數
      if (!params[v]) { // <-- 如果需要的參數不在,可以自己處理
        return navigate('/404')  // <-- 我這邊是直接丟去404
      }

      path = path.replace(k, params[v]) // <-- 取代 path
    }

    navigate(path) // 最後做轉址
  }
}

export default useParamNavigate

這樣就完成了,至於第一行的 import 'core-js/features/string/match-all' ,有興趣可以參考我上一篇文章的 關於 Polyfill




最後更新時間: 2022年05月23日.