298 lines
5.6 KiB
Markdown
Executable File
298 lines
5.6 KiB
Markdown
Executable File
|
|
# React Router(V6)
|
|
|
|
[Tutorial v6.8.1 | React Router](https://reactrouter.com/en/main/start/tutorial)
|
|
|
|
> v6不兼容 v5, 变化较大
|
|
|
|
```sh
|
|
# 安装最新版v6
|
|
pnpm add react-router-dom
|
|
```
|
|
|
|
## helloRouter
|
|
|
|
```jsx
|
|
/**
|
|
* react router 使用步骤
|
|
* 1. 安装react-router-dom包
|
|
* 2. 在main.jsx 中引入BrowserRouter组件
|
|
* 3. 将BrowserRouter设置为根组件
|
|
* 4. 在App.jsx 中引入 Routes, Route 组件
|
|
* 5. 用 Routes组件包裹Route组件
|
|
*/
|
|
```
|
|
|
|
```jsx
|
|
/**
|
|
* Routes v6新增的组件
|
|
* 类似v5的Switch, 都是用于Route的容器
|
|
* Routes中的Route只有一个会被匹配 不用加exact了
|
|
* v6中 component render children 都不能用了
|
|
* 改为element指定要挂载的组件
|
|
*/
|
|
```
|
|
|
|
```jsx
|
|
// @/main.jsx
|
|
import React from 'react'
|
|
import ReactDOM from 'react-dom/client'
|
|
import App from './App'
|
|
import {BrowserRouter as Router} from 'react-router-dom'
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
<Router>
|
|
<App />
|
|
</Router>
|
|
)
|
|
```
|
|
|
|
```jsx
|
|
// @/App.jsx
|
|
import { Routes, Route } from 'react-router-dom'
|
|
import Menu from './components/Menu'
|
|
import Home from './components/Home'
|
|
import About from './components/About'
|
|
|
|
function App() {
|
|
return (
|
|
<div className="App">
|
|
<Menu />
|
|
<Routes>
|
|
<Route path='/' element={<Home />} />
|
|
<Route path='/about' element={<About />} />
|
|
</Routes>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|
|
```
|
|
|
|
```jsx
|
|
// @/components/Home.jsx
|
|
const Home = ()=>{
|
|
return (
|
|
<h2>主页</h2>
|
|
)
|
|
}
|
|
|
|
export default Home
|
|
```
|
|
|
|
```jsx
|
|
// @/components/About.jsx
|
|
const About = ()=>{
|
|
return (
|
|
<div>
|
|
<h2>关于我们</h2>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default About
|
|
```
|
|
|
|
```jsx
|
|
// @/components/Menu.jsx
|
|
import { Link } from 'react-router-dom'
|
|
|
|
const Menu = () => {
|
|
return (
|
|
<div>
|
|
<ul>
|
|
<li>
|
|
<Link to="/">主页</Link>
|
|
</li>
|
|
<li>
|
|
<Link to="/about">关于</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Menu
|
|
```
|
|
|
|
## 钩子
|
|
|
|
```jsx
|
|
// useParams() 不变
|
|
// useLocation() 不变
|
|
|
|
// [x] useRouteMatch() 没有了
|
|
// useMatch(path) 检查当前url是否匹配某个路由,需要传参, 例如 useMatch('/about'),不匹配返回null, 匹配返回对象
|
|
|
|
// [x] useHistory() 没有了
|
|
// useNavigage() 获取一个用来跳转的函数
|
|
// ...
|
|
import {useNavigate} from 'react-router-dom'
|
|
// ...
|
|
const nav = useNavigate()
|
|
const clickHandler = ()=>{
|
|
nav('/about') // 默认使用push跳转,有历史记录
|
|
nav('/about',{replace:true}) // 使用replace跳转
|
|
}
|
|
```
|
|
|
|
## 嵌套路由+ Outlet
|
|
|
|
```jsx
|
|
/**
|
|
* V6默认严格匹配,想取消,可以 path='/about/*'
|
|
* Outlet 用来表示嵌套路由中的组件
|
|
* 当路径匹配成功, Outlet表示嵌套路由中的组件
|
|
* 当匹配失败, Outlet相当于不存在
|
|
* 有点像vue的插槽
|
|
*/
|
|
```
|
|
|
|
```jsx
|
|
// @/App.jsx
|
|
// Route嵌套Route
|
|
// '/about'可以省略成'about', '/about/hello'省略成'hello'
|
|
|
|
import { Routes, Route } from 'react-router-dom'
|
|
import Menu from './components/Menu'
|
|
import Home from './components/Home'
|
|
import About from './components/About'
|
|
import Hello from './components/Hello'
|
|
import Word from './components/Word'
|
|
|
|
function App() {
|
|
return (
|
|
<div className="App">
|
|
<Menu />
|
|
<Routes>
|
|
<Route path='/' element={<Home />} />
|
|
<Route path='about' element={<About />} >
|
|
<Route path='hello' element={<Hello />} />
|
|
<Route path='word' element={<Word />} />
|
|
</Route>
|
|
</Routes>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|
|
```
|
|
|
|
```jsx
|
|
// @/components/About
|
|
// 添加Outlet组件
|
|
// 会智能匹配 hello组件或者word组件
|
|
|
|
import { Outlet } from 'react-router-dom'
|
|
|
|
const About = ()=>{
|
|
return (
|
|
<div>
|
|
<h2>关于我们</h2>
|
|
<Outlet />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default About
|
|
```
|
|
|
|
## Navigate组件
|
|
|
|
> V5使用 Redirect 重定向, V6使用 Navigate 组件
|
|
|
|
```jsx
|
|
// 默认使用push跳转
|
|
{!isLogin && <Navigate to="/login" />}
|
|
|
|
// 添加replace使用replace跳转
|
|
{!isLogin && <Navigate replace to="/login" />}
|
|
|
|
// 传递参数
|
|
{!isLogin && <Navigate replace to="/login" state={arg:'test'}/>} // 在跳转后的页面用useLocation()可以拿到
|
|
```
|
|
|
|
## NavLink组件
|
|
|
|
> V5的 activeStyle activeClassName 取消了
|
|
> V6 改成style={()=>{}}
|
|
|
|
1. 使用style
|
|
|
|
```jsx
|
|
<NavLink
|
|
to="/about/word"
|
|
style={({ isActive }) => {
|
|
return isActive ? { backgroundColor: 'yellow' } : null
|
|
}}>
|
|
about/word
|
|
</NavLink>
|
|
```
|
|
|
|
2. 使用className
|
|
|
|
```jsx
|
|
import classes from './Menu.module.css'
|
|
// ...
|
|
<NavLink
|
|
to="/about/hello"
|
|
className={({isActive}) => {
|
|
return isActive ? classes.active : ''
|
|
}}>
|
|
about/hello
|
|
</NavLink>
|
|
```
|
|
|
|
```css
|
|
/* Menu.module.css */
|
|
a.active {
|
|
background-color: green;
|
|
}
|
|
```
|
|
|
|
## 高阶组件实现路由守卫
|
|
|
|
```jsx
|
|
// 没有登录访问权限页面,跳转登录页面
|
|
<Route
|
|
path="/profile"
|
|
element={isLogin ? <Profilepage /> : <Navigate replace to="/login" />}
|
|
/>
|
|
```
|
|
|
|
提取成组件
|
|
|
|
```jsx
|
|
import { useSelector } from 'react-redux'
|
|
import { Navigate } from 'react-router-dom'
|
|
|
|
const NeedAuth = props => {
|
|
const { isLogin } = useSelector(state => state.auth)
|
|
return isLogin ? props.children : <Navigate replace to="/login" />
|
|
}
|
|
|
|
export default NeedAuth
|
|
```
|
|
|
|
```jsx
|
|
<Route
|
|
path="/profile"
|
|
element={<NeedAuth><Profilepage /></NeedAuth>}
|
|
/>
|
|
```
|
|
|
|
想登录成功后返回原来的页面, 使用 useLocation 和 Navigate state 来传递和获取 path
|
|
|
|
```jsx
|
|
import { useSelector } from 'react-redux'
|
|
import { Navigate,useLocation } from 'react-router-dom'
|
|
|
|
const NeedAuth = props => {
|
|
const location = useLocation()
|
|
const { isLogin } = useSelector(state => state.auth)
|
|
return isLogin ? props.children : <Navigate replace to="/login" state={{prevLocation:location}} />
|
|
}
|
|
|
|
export default NeedAuth
|
|
|
|
``` |