React Router 6 概述
-
React Router 以三个不同的包发布到 npm
上,它们分别为:
react-router
: 路由的核心库,提供了很多的:组件、钩子。
react-router-dom
: 包含react-router
所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
等。
react-router-native
: 包括react-router
所有内容,并添加一些专门用于ReactNative
的API,例如:<NativeRouter>
等。
-
与React Router 5.x 版本相比,改变了什么?
- 内置组件的变化:移除
<Switch/>
,新增 <Routes/>
等。
- 语法的变化:
component={About}
变为 element={<About/>}
等。
- 新增多个hook:
useParams
、useNavigate
、useMatch
等。
- 官方明确推荐函数式组件了!!!
准备工作
创建新项目:
1
| npx create-react-app router6
|
目前create-react-app
默认创建为React 18,先降到17版本:
1
| npm install react@17.x react-dom@17.x --save
|
安装react-router-dom
:
若以上命令出现报错,则加后缀:--legacy-peer-deps
编写基本内容:
-
项目结构
-
index.js
1 2 3 4 5 6 7 8 9 10 11
| import React from "react" import ReactDOM from "react-dom" import { BrowserRouter } from "react-router-dom" import App from "./App"
ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') )
|
App.jsx声明函数式组件
一级路由
在React Router 6
中,<Route />
的component
属性已替换为element
,Switch
已弃用并替换为Routes
,值得注意的是,在React Router 5
中,用<Switch></Switch>
包裹<Route />
不是必须的,但在React Router 6
中必须用<Routes></Routes>
包裹<Route />
。
App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import React from 'react' import { NavLink, Routes, Route } from 'react-router-dom' import About from './pages/About' import Home from './pages/Home'
export default function App() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} <NavLink className="list-group-item" to="/about">About</NavLink> <NavLink className="list-group-item" to="/home">Home</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Routes> <Route path='/about' element={<About/>} /> <Route path='/home' element={<Home/>} /> </Routes> </div> </div> </div> </div> </div> ) }
|
重定向
React Router 6
已将原有的Redirect
删除,现在使用Navigate
只要<Navigate />
组件被渲染,就会修改路径、切换视图
to
属性:重定向地址
replace
属性:false为push,true为replace
使用:嵌套在Route
标签中使用
1
| <Route path='/' element={<Navigate to='/about' />} />
|
需求:点击使sum+1并实现路由跳转
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React, {useState} from 'react' import { Navigate } from 'react-router-dom'
export default function Home() { const [sum, setSum] = useState(1) return ( <> <h3>我是Home的内容</h3> {sum === 2 ? <Navigate to="/about" /> : <h4>当前sum的值是:{sum}</h4>} <button onClick={() => setSum(2)}>点击sum+1</button> </> ) }
|
NavLink
作用: 与<Link>
组件类似,且可实现导航的“高亮”效果,与Router 5不同的是,不在使用activeClassName
,而是如下所示:
1 2 3 4 5 6 7
| <NavLink to="login" className={({ isActive }) => { console.log('home', isActive) return isActive ? 'base one' : 'base' }} >login</NavLink>
|
默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,当NavLink
上添加了end
属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
路由表
使用useRoutes
Hook,根据路由表,动态创建<Routes>
和<Route>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import React from 'react' import { NavLink, Navigate, useRoutes } from 'react-router-dom' import About from './pages/About' import Home from './pages/Home'
export default function App() { const element = useRoutes([ { path: '/about', element: <About/> }, { path: '/home', element: <Home/> }, { path: '/', element: <Navigate to="/about"/> } ])
return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} <NavLink className="list-group-item" to="/about">About</NavLink> <NavLink className="list-group-item" to="/home">Home</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} {element} </div> </div> </div> </div> </div> ) }
|
嵌套路由
路由表写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { Navigate } from "react-router-dom" import About from "../pages/About" import Home from "../pages/Home" import Message from "../pages/Message" import News from "../pages/News"
export default [ { path: '/about', element: <About /> }, { path: '/home', element: <Home />, children: [ { path: 'message', element: <Message /> }, { path: 'news', element: <News /> }, ] }, { path: '/', element: <Navigate to="/about" /> } ]
|
Home组件:使用<Outlet />
,当<Route>
产生嵌套时,渲染其对应的后续子路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from 'react' import { NavLink, Outlet } from 'react-router-dom'
export default function Home() { return ( <div> <h2>Home组件内容</h2> <div> <ul className="nav nav-tabs"> <li> <NavLink className="list-group-item" to="news">News</NavLink> {/* 不能写斜线 */} </li> <li> <NavLink className="list-group-item" to="message">Message</NavLink> </li> </ul> {/* 指定路由组件呈现的位置 */} <Outlet /> </div> </div> ) }
|
传递参数
params参数
-
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
-
在路由表中声明接收参数:
1 2 3 4 5 6 7 8
| path: 'message', element: <Message />, children: [ { path: 'detail/:id/:title/:content', element: <Detail /> } ]
|
-
接收参数:const {id, title, content} = useParams()
Message组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React, { useState } from 'react' import { Link, Outlet } from 'react-router-dom'
export default function Message() {
const [messages] = useState([ {id: '001', title: 'message1', content: 'abcdefg'}, {id: '002', title: 'message2', content: 'hijklmn'}, {id: '003', title: 'message3', content: 'opqrst'}, {id: '004', title: 'message4', content: 'uvwxyz'}, ])
return ( <div> <ul> { messages.map((message) => { return ( <li key={message.id}> <Link to={`detail/${message.id}/${message.title}/${message.content}`}>{message.title}</Link> </li> ) }) } </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ) }
|
Detail组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react' import { useMatch, useParams } from 'react-router-dom'
export default function Detail() { const {id, title, content} = useParams() const x = useMatch('/home/message/detail/:id/:title/:content') console.log(x) return ( <ul> <li>id: {id}</li> <li>title: {title}</li> <li>content: {content}</li> </ul> ) }
|
search参数
- 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
- 注册路由(无需声明,在路由表中正常注册即可)
- 接收参数:
const [search, setSearch] = useSearchParams()
Detail组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from 'react' import { useLocation, useSearchParams } from 'react-router-dom'
export default function Detail() { const [search, setSearch] = useSearchParams() const id = search.get('id') const title = search.get('title') const content = search.get('content') const x = useLocation() console.log(x) return ( <ul> <li> <button onClick={() => setSearch('id=005&title=阿巴阿巴&content=ohhhhhh')}>点击修改search参数</button> </li> <li>id: {id}</li> <li>title: {title}</li> <li>content: {content}</li> </ul> ) }
|
state参数
传递的内容不会在地址栏展示,与组件的状态(state)不同
-
路由链接(携带参数):
1 2 3 4 5 6 7 8
| <Link to='detail' state={{ id: message.id, title: message.title, content: message.content }}>{message.title} </Link>
|
-
注册路由(无需声明,在路由表中正常注册即可)
-
接收参数:const {state: {id, title, content}} = useLocation()
-
备注:刷新也可以保留住参数,但清除浏览器数据后不可
编程式路由导航
需求:鼠标滑过时引起组件渲染,这时<Link/> <NavLink/> <Navigate/>
都不能胜任
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import React, { useState } from 'react' import { Link, Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const [messages] = useState([ {id: '001', title: 'message1', content: 'abcdefg'}, {id: '002', title: 'message2', content: 'hijklmn'}, {id: '003', title: 'message3', content: 'opqrst'}, {id: '004', title: 'message4', content: 'uvwxyz'}, ])
const navigate = useNavigate()
function show(message) { navigate('detail', { replace: false, state: { id: message.id, title: message.title, content: message.content } }) }
return ( <div> <ul> { messages.map((message) => { return ( <li key={message.id}> <Link to='detail' state={{ id: message.id, title: message.title, content: message.content }}>{message.title} </Link> <button onClick={() => show(message)}>展示详情</button> </li> ) }) } </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ) }
|
扩展:前进与后退
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from 'react' import { useNavigate } from 'react-router-dom'
export default function Header() { const navigate = useNavigate() function back() { navigate(-1) } function forward() { navigate(1) } return ( <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> <button onClick={back}>👈后退</button> <button onClick={forward}>前进👉</button> </div> </div> ) }
|
其他的Hooks
useInRouterContext()
主要用于判断当前组件(一般组件和路由组件)是否处于路由的上下文环境中,即组件被BrowserRouter
或HashRouter
包裹(包括子组件),不在路由的上下文环境中,即脱离了路由器的管理
返回一个布尔值,处于路由的上下文环境中时返回真。
useNavigationType()
返回当前的导航类型
- POP:在浏览器中直接打开或刷新页面
- PUSH
- REPLACE
useOutlet()
用来呈现当前组件中渲染的嵌套路由
console.log(useOutlet())
- 如果嵌套路由没有挂载,则输出null
- 如果嵌套路由已经挂载,则展示嵌套的路由对象
useResolvedPath()
给定一个URL值,解析其中的path
、search
、hash
值
console.log(useResolvedPath('/user?id=01&name=abc&se=hi#goto'))