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
組件,不會在Tab1
和Tab2
切換時被覆蓋需要使用嵌套路由。
嵌套路由
嵌套路由,可以保證子路由共享父路由的界面而不會覆蓋。為此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在交互過程中,當我們點擊Tab1
和Tab2
進行切換時,或者點擊apple
或appet
時,會出現輸入框被清空,且列表不再被過濾的問題。
react-router
提供了useLocation
方法,它返回瀏覽器顯示的url
信息。通過它可以獲取瀏覽器url
中的搜索參數,從而進行暫存,在具體組件內,可以通過useSearchParams
獲取到暫存的值。具體方式,通過自定義組件包裝NavLink
或Link
來實現。
///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))
}