React 路由使用

Router

react-router-dom是一個處理頁面跳轉的三方庫,在使用之前需要先安裝到我們的項目中:

# npm
npm install react-router-dom@6
#yarn
yarn add react-router-dom@6

簡單路由

使用路由時需要為組件指定一個路由的path,最終會以path為基礎,進行頁面的跳轉。具體使用先看個簡單示例,該示例比較簡單就是兩個Tab頁面的來回切換。

///導入路由
import {Link} from 'react-router-dom'
function App() {
  return (
    <div>
      <h1>路由練習</h1>
      <nav>
        {/* link 頁面展示時,是個a標簽 */}
        <Link className ='link' to='/Tab1'> Tab1</Link> ///覆蓋:渲染tab1組件
        <Link className = 'link' to='/Tab2'> Tab2 </Link> ///覆蓋:渲染tab2組件
      </nav>
    </div>
    );
}
///路由頁面1
export default function Tab1(params) {
    return (
        // 文檔中,<main> 元素是唯一的,所以不能出現一個以上的 <main> 元素
        <main style={{ padding: "1rem 0" }}>
          <h2>我是Tab1</h2>
        </main>
      );
}
///路由頁面2
export default function Tab2(params) {
    return (
        <main style={{ padding: "1rem 0" }}>
          <h2>我是Tab2</h2>
        </main>
      );
}
///在index.js中配置路由
import {BrowserRouter,Routes,Route} from 'react-router-dom'
import Tab1 from './pages/Tab1.jsx'
import Tab2 from './pages/Tab2.jsx'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
     <Routes>
       <Route path = '/' element = {<App/>} /> ///兄弟路由
       <Route path = '/Tab1' element = {<Tab1/>} />///兄弟路由
       <Route path = '/Tab2' element = {<Tab2/>} />///兄弟路由
     </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

最終交互時,上述路由配置會出現彼此覆蓋的情況,如下圖:

[圖片上傳失敗...(image-e7e5d4-1654521601103)]

為了保證App組件,不會在Tab1Tab2切換時被覆蓋需要使用嵌套路由。

嵌套路由

嵌套路由,可以保證子路由共享父路由的界面而不會覆蓋。為此React提供了Outlet組件,將其用于父組件中可以為子路由的元素占位,并最終渲染子路由的元素。

Outlet渲染一個子路由的元素

import {Link,Outlet} from 'react-router-dom'
function App() {
  return (
    <div>
      <h1>路由練習</h1>
      <nav>
        {/* link 頁面展示時,是個a標簽 */}
        <Link className ='link' to='/Tab1'> Tab1</Link> 
        <Link className ='link' to='/Tab2'> Tab2 </Link>
      </nav>
      {/* 此時尚不能實現共享APP UI的同時渲染出 Tab1 和 Tab2,還需要使用 <Outlet/>
        保證父路由,在子路由交換時,仍然存在
      */}
      <Outlet/>
    </div>
    );
}

///index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
       <Route path='/' element={<App />} >
          {/* 孩子路由,url為:  / + 孩子的path */}
          <Route path='Tab1' element={<Tab1 />} /> 
          <Route path='Tab2' element={<Tab2 />} />
        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

最終效果如下圖:

[圖片上傳失敗...(image-491108-1654521601104)]

未匹配路由

通過path='*',實現沒有其他路由匹配時,對其進行匹配。

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
      <Route path='/' element={<App />} >
          {/* 孩子路由,url為:  / + 孩子的path */}
          <Route path='Tab1' element={<Tab1 />} /> 
          <Route path='Tab2' element={<Tab2 />} />
          <Route path = '*' element={<p>
            未匹配到路由時,會跳轉此處。
          </p>} />
        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

效果如圖:

[圖片上傳失敗...(image-c2afa2-1654521601104)]

路由傳參數

通過路由傳遞參數到組件中

///模擬數據
const dataList = [
    {
        id:20220101,
        content:'筆記1'
    },
    {
        id:20220102,
        content:'筆記2'
    },
    {
        id:20220103,
        content:'筆記3'
    },
]
export default function getTodoList(params) {
    return dataList
}

export function findTodoItem(params) {
    return dataList.find((value)=>value.id === params)
}
///組件Tab2中定義列表
export default function Tab2(params) {
    let list = getTodoList()
    return (
        <div>
            <ul>
                {
                    list.map((item) => (
                        <li key={item.id}>
                           {/*子路由形如:'/Tab2/20220103' */}
                            <Link to={`/Tab2/${item.id}`}>{item.content}</Link>
                        </li>
                    ))
                }
            </ul>
           {/*渲染一個子路由的元素*/}
            <Outlet />
        </div>
    );
}

///注冊列表項的子路由
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
      <Route path='/' element={<App />} >
          {/* 孩子路由,url為:  / + 孩子的path */}
          <Route path='Tab1' element={<Tab1 />} /> 
          <Route path='Tab2' element={<Tab2 />} >
            <Route path=':itemId' element={<ItemDetail/>}/>
          </Route>
          <Route path = '*' element={<p>未匹配到該路由請先設置路由頁面 </p>} />
        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

///定義Tab2子組件 ItemDetail
import { useParams } from 'react-router-dom'
export function ItemDetail() {
    //點擊每一項的鏈接,注意:URL 發生了變化,但新的組件尚未顯示
    ///需要父組件中添加<Outlet>
    ///HOOK 獲取路由中的參數,形如{itemId:'20220102'}
    let params = useParams()
    let content = findTodoItem(parseInt(params.itemId)).content
    return (
        <div>
            <h2>筆記詳情</h2>
            <p>這是我于{params.itemId},記錄的筆記他的內容為{content}</p>
        </div>
    )
}

索引路由

當我們切換至Tab1再切回Tab2后,筆記詳情頁面將空白。

可以通過索引路由填補空白,具體只需:

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
      <Route path='/' element={<App />} >
          {/* 孩子路由,url為:  / + 孩子的path */}
          <Route path='Tab1' element={<Tab1 />} /> 

          <Route path='Tab2' element={<Tab2 />} >
            {/*索引路由 有index 無path*/}
            <Route index element={<p>請選擇一個筆記查看它的詳情 </p>}/>
            <Route path=':itemId' element={<ItemDetail/>}/>
          </Route>
          
          <Route path = '*' element={<p>未匹配到該路由請先設置路由頁面 </p>} />

        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

當父路由匹配,但其他子路由都不匹配時,由索引路由匹配。索引路由是父路由的默認子路由。
當用戶尚未單擊導航列表中的一項時,會呈現索引路由。

活動鏈接

Link功能一致,差異是可以設置點擊后的顏色

export default function Tab2(params) {
    let list = getTodoList()
    return (
        <div>
            <ul>
                {
                    list.map((item) => (
                        <li key={item.id}>
                            {/* <Link to={`/Tab2/${item.id}`}>{item.content}</Link> */}
                            <NavLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </NavLink>
                        </li>
                    ))
                }
            </ul>
            <Outlet />
        </div>
    );
}

搜索參數

搜索參數類似于 URL 參數,形如/login?success=1

export default function Tab2(params) {
    let list = getTodoList()
    ///和React.useState很像
    let [searchParams, setSearchParams] = useSearchParams();
    return (
        <div>
            {/* 搜索框: 隨著輸入設置搜索參數 */}
            <input type="text" onChange = { (event)=>{
                let text = event.target.value
                if (text) {
                    setSearchParams({text})
                } else {
                    setSearchParams({})
                }
            } } />

            <ul>
                { list.filter((item)=>{
                    let txt = searchParams.get('text')
                    if (!txt) return true
                    return item.content.startsWith(txt)
                })
                    .map((item) => (
                        <li key={item.id}>
                            {/* <Link to={`/Tab2/${item.id}`}>{item.content}</Link> */}
                            <NavLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </NavLink>
                        </li>
                    ))
                }
            </ul>

            <Outlet />
        </div>
    );
}

隨著我們輸入apple, 路由的地址將變為/Tab2?text=apple觸發路由重新呈現。

當我們在輸入框輸入字符時,便會觸發列表的過濾顯示。

自定義行為

上述UI在交互過程中,當我們點擊Tab1Tab2進行切換時,或者點擊appleappet時,會出現輸入框被清空,且列表不再被過濾的問題。

react-router提供了useLocation方法,它返回瀏覽器顯示的url信息。通過它可以獲取瀏覽器url中的搜索參數,從而進行暫存,在具體組件內,可以通過useSearchParams獲取到暫存的值。具體方式,通過自定義組件包裝NavLinkLink來實現。

///1.自定義`QueryLink`組件
import React, { Component } from 'react';
import { useLocation,NavLink } from "react-router-dom";
export default function QueryLink({to,...props}) {
  ///代表當前瀏覽器顯示的url
  // location內容:{pathname: '/Tab2', search: '?text=ad', hash: '', state: null, key: 'dhvg8xme'}
  let location = useLocation()
  // to = '/Tab2?text=ad'
  return <NavLink to={to+location.search} {...props} />
}
//2. 替換 `tab1`、`tab2`的link or Navlink
<QueryLink className ='link' to='/Tab1'> Tab1</QueryLink> 
<QueryLink className ='link' to='/Tab2'> Tab2 </QueryLink>
//3. 替換列表項的link or Navlink
 <li key={item.id}>
   <QueryLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </QueryLink>
 </li>

useNavigate

上述示例中,路由的切換采用Link或者NavLink,但當我們的頁面元素不使用Link時,比如使用Button,此時便需要使用采用useNavigate。同上可以配合useLocation保存搜索字段。

export function ItemDetail() {
    //點擊每一項的鏈接,注意:URL 發生了變化,但新的組件尚未顯示
    ///需要父組件中添加<Outlet>
    let params = useParams()
    let content = findTodoItem(parseInt(params.itemId)).content
    ///返回函數
    let navigate = useNavigate()
    ///獲取搜索字段
    let location = useLocation()
    return (
        <div>
            <h2>筆記詳情</h2>
            <p>這是我于{params.itemId}記錄的筆記,內容為{content}</p>
            <button onClick = {
                (e)=>{
                    deleteTodoItem(params.itemId)
                    navigate('/Tab2/'+location.search)
                }
            }>
                刪除筆記
            </button>
        </div>
    )
}
// src/data.jsx
export function deleteTodoItem(params) {
    dataList = dataList.filter((value)=>value.id !== parseInt(params)) 
}

參考資料

https://reactrouter.com/docs/en/v6/getting-started/tutorial

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • Router react-router-dom是一個處理頁面跳轉的三方庫,在使用之前需要先安裝到我們的項目中: 簡...
    QiShare閱讀 896評論 0 1
  • Vue全家桶-前端路由 一、路由的基本概念與原理 1.路由:是一個比較廣義和抽象的概念,路由的本質就是對應關系。 ...
    coder_shen閱讀 371評論 0 0
  • 基本使用 1.導入js文件 2.添加路由鏈接 3.添加路由占位符(最后組件展示的位置) router-view:路...
    過盡千帆_YL閱讀 1,331評論 0 10
  • 在Vue中,通常使用Vue Router進行路由配置。 對于路由,我們可以新建一個叫做router.js的文件,然...
    MarkOut閱讀 1,595評論 0 0
  • 路由的概念: 路由就是Hash地址與組件之間對應關系。 SPA指的是一個web網站只有一個唯一的一個HTML頁面,...
    沃德麻鴨閱讀 2,767評論 0 11