React 路由使用
QiShare 人气:1Router
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> );
最终交互时,上述路由配置会出现彼此覆盖的情况,如下图:
为了保证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> );
最终效果如下图:
未匹配路由
通过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> );
效果如图:
路由传参数
通过路由传递参数到组件中
///模拟数据 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)) }
参考资料
加载全部内容