本文章是react的路由笔记
根据浏览器历史记录:history(BOM)
react-router
:(有三种库)react-router-dom
的api(版本5/v5)npm i react-router-dom@5
1.
2.
3.
4.
5.
6.
7.
App.js中
import { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
export default class App extends Component {
render() {
return {
<div>
<header>
{/* 编写路由链接 */}
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
</header>
<main>
{/* 注册路由 */}
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</main>
</div>
}
}
}
index.js中
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, HashRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
{/* history路由 */}
<BrowserRouter>
<App />
</BrowserRouter>
{/* hash路由 */}
<HashRouter></HashRouter>
, document.querySelect('#root'))
一般组件的props信息需要通过父组件传递,而路由组件的props信息是路由信息,不需要传递。
NavLink
的基本使用匹配上的路由会默认加上active
这个类名
<NavLink to="/home">首页</NavLink>
<NavLink to="/about">关于</NavLink>
通过activeClassName
可以自定义选中的类名
<NavLink to="/home" activeClassName="header-active">首页</NavLink>
<NavLink to="/about" activeClassName="header-active">关于</NavLink>
NavLink
的封装组件的使用:
{/* 「首页」标题:组件标签标签体内容(特殊的标签属性,组件内可以通过this.props.children获取) */}
<MyNavLink to="/home">首页</MyNavLink>
<MyNavLink to="/about">关于</MyNavLink>
组件的封装:
{/* 一组props */}
<NavLink to={to} activeClassName="header-active">首页</NavLink>
{/* 多组props */}
<NavLink {...this.props} activeClassName="header-active"/>
Switch
:当匹配到第一个路由的时候就停止
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/about" component={Demo} />
</Switch>
模糊匹配:
<header>
<NavLink to="/about/a/b">关于</NavLink>
</header>
<main>
<Switch>
<Route path="/about" component={About} />
</Switch>
</main>
精准匹配:exact={true}
<Route exact={true} path="/about" component={About} />
Redirect(重定向):谁都匹配不上,就走Redirect
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/about" />
</Switch>
嵌套路由要写完整路径
<div>
<NavLink to="/home/news">News</NavLink>
<NavLink to="/home/message">Message</NavLink>
</div>
<div>
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
</Switch>
</div>
params
参数传递的参数:
<NavLink to={`/home/news/${title}/${page}`}>News</NavLink>
<Route path="/home/news/:title/:page" component={News} />
News组件可以通过this.props.match.params
获取到params
参数
search(query)
参数(显式传参)使用:
<NavLink to={`/home/news?title=${title}&page=${page}`}>News</NavLink>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/news" component={News} />
接收:
接收到的search参数是urlencoded编码的字符串,需要借助querystring解析
key=value&key=value => urlencoded编码
安装query-string
npm i query-string
import qs from 'query-string'
qs.parse(this.props.location.search)
state
参数(隐式传参)使用:
<NavLink to={{pathname: '/home/news', state: { title, page }}}>News</NavLink>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/news" component={News} />
接收:
通过this.props.location.state
接收,BrowserRouter
刷新参数不丢失,HashRouter
刷新参数丢失
// 路由跳转
this.props.history.replace('/xxx', [state])
this.props.history.replace('/xxx', { id, title })
this.props.history.push('/xxx', [state])
this.props.history.push('/xxx', { id, title })
// 路由前进
this.props.history.goForward()
// 路由后退
this.props.history.goBack()
// 与vue中的go类似,传入number,正数表示前进,负数表示后退
this.props.history.go(number)
在一般组件内,不能像路由组件一样获取到路由信息,需要在组件上包裹一个withRouter
。withRouter
可以加工一般组件,让一般组件具备路由组件所特有的api,withRouter
的返回值是一个新组件。
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class App extends Component {
handleClick = () => {
console.log(this.props)
}
render() {
return (
<div onClick={this.handleClick}>关于</div>
)
}
}
export default withRouter(App)
和
v6版本中移除了之前的
,用
代替
和
要配合使用,且必须要用
包裹
属性用于指定:匹配时是否区分大小写(默认为false)
也可以嵌套使用,且可配合useRoutes()
配置路由表,但需要通过
组件来渲染其子路由
的end
属性:如果他的子路由匹配,父亲路由失去高亮
写法区别:
import { Routes, Route, Navigate } from 'react-router-dom'
{/* Switch组件 替换成 Routes */}
<Routes>
{/* Route的component属性变成element属性 */}
{/* v5写法: */}
{/* v6写法: } /> 写成标签 */}
<Route caseSensitive path='/home' element={<Home />} />
<Route path='/about' element={<About />} />
{/* 重定向,不用Redirect,而是Navigate */}
{/* 用法: */}
{/* replace跳转模式:push/replace模式,默认false(push) */}
<Route path='/' element={<Navigate to="/home" />} />
{/* end属性:如果他的子路由匹配,父亲路由失去高亮 */}
<Route path='/' end element={<Navigate to="/home" />} />
</Routes>
自定义选中的高亮样式
{/* v5写法 */}
<NavLink activeClassName="xxx">
{/* v6写法:在className属性中写入函数,函数返回类名 */}
<NavLink to='/home' className={({isActive}) => { return isActive ? 'item active' : 'item' }}>Home</NavLink>
<NavLink to='/home' className={(e) => {console.log(e);}}>Home</NavLink>
下面是的参数e:
:重定向,不用Redirect,而是Navigate。渲染到页面上,会让路由进行跳转。
注册路由:
{/* 注册路由 */}
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/' element={<Navigate to="/home" />} />
</Routes>
useRoutes
生成路由表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 routeElement = useRoutes([
{
path: '/home',
element: <Home />
},
{
path: '/about',
element: <About />
},
{
path: '/',
element: <Navigate to="/home" />
}
])
return (
<div>
<NavLink to='/home'>Home</NavLink> | <NavLink to='/about'>About</NavLink>
<hr />
{/* 注册路由 */}
{ routeElement }
</div>
)
}
在路由表添加children
属性即可
const routeElement = useRoutes([
{
path: '/home',
element: <Home />
},
{
path: '/about',
element: <About />,
children: [
{
path: 'news',
element: <div>news</div>
},
{
path: 'message',
element: <div>message</div>
}
]
},
{
path: '/',
element: <Navigate to="/home" />
}
])
在About
组件中,需要一个路由出口,用Outlet
来展示二级子路由
import React from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function index() {
return (
<div>About
<br />
{/* v5写法 */}
<NavLink to="/about/news">news</NavLink> | <NavLink to="/about/message">message</NavLink>
{/* v6中:同v5,也可以只写子路由路径,但是不能带斜杠,类似于vue */}
<NavLink to="news">news</NavLink> | <NavLink to="message">message</NavLink>
{/* 路由出口 */}
<Outlet />
</div>
)
}
useParams()
获取传递的params参数
import { useParams } from 'react-router-dom'
useMatch
同useParams
都可以用来读取params参数
使用:useMatch('/home/:id/:title')
useSearchParams
用来获取search(query)
参数,返回的是一个数组
get()
方法获取// 我的url为http://localhost:5173/about/news?id=1&title=haha
const [searchParams, setSearchParams] = useSearchParams()
// 需要通过get方法,传入key,获取到value
let id = searchParams.get('id'))
setSearchParams('id=2&title=hello')
search
参数:useLocation
同useSearchParams
都可以获取search
参数,不用像useMatch
那样传递路径。
state
参数:传递state
参数(隐式传参):
{/* v5写法 */}
<NavLink to={{ pathname: '/home', state: {name: 'xiaotian', age: 20} }}>Home</NavLink>
{/* v6写法 */}
<NavLink to='/home' state={{ name: 'xiaotian', age: 20 }}>Home</NavLink>
useLocation()
// 对象的连续解构
const { state: {name, age} } = useLocation()
params => useParams、useMatch
search(query) => useSearchParams、useLocation
state => useLocation
let navigate = useNavigate()
// 1. 基本使用
navigate('/about')
// 2. 子路由
navigate('news')
// 3. 传递state参数
navigate('news', {
replace: false,
state: {
name: 'xiaotian',
age: 20
},
})
// 4. 前进
navigate(1)
// 5. 后退
navigate(-1)
pop
、push
、replace
pop
是指直接在浏览器中打开了这个路由组件(复制链接、刷新进入)null
;如果嵌套路由已经挂载,则展示嵌套路由对象。let res = useOutlet()
作用:给定一个url值,解析其中的path
、search
、hash
值。
src
下创建router
目录,新建index.jsx
这里用到了嵌套路由的写法
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import App from '../App'
import Home from '../views/Home/Home'
import About from '../views/About/About'
const baseRouter = () => {
return (
<BrowserRouter>
<Routes>
{/* 嵌套路由写法 */}
<Route path='/' element={<App />}>
<Route path='/home' element={<Home />}></Route>
<Route path='/about' element={<About />}></Route>
</Route>
</Routes>
</BrowserRouter>
)
}
export default baseRouter
main.jsx
中将原有的App
根组件替换成路由表组件import ReactDOM from 'react-dom/client'
import BaseRouter from './router/index.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<BaseRouter />,
)
App.jsx
中配置路由出口import { Outlet } from 'react-router-dom'
export default function App() {
return (
<div>
{/* 占位符,用于展示组件,类似于vue的router-view(路由出口) */}
<Outlet />
</div>
)
}
src
下创建router
目录,新建index.jsx
import { Navigate } from 'react-router-dom'
import Home from '../views/Home/Home'
import About from '../views/About/About'
const routes = [
// 当App组件使用了useRoutes渲染路由时,就不允许在路由表中配置App组件有关的路由
// 错误写法:会导致页面不停渲染,最终卡死
// {
// path: '/',
// element:
// },
{
path: '/',
element: <Navigate to="/home" />
},
{
path: '/home',
element: <Home />
},
{
path: '/about',
element: <About />
},
// 错误页面,重定向到404
{
path: '*',
element: <Navigate to="/404" />
}
]
export default routes
main.jsx
中用BrowserRouter
或HashRouter
包裹一下根组件import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>,
)
App.jsx
中渲染路由import { useRoutes } from 'react-router-dom'
import router from './router'
export default function App() {
const outlet = useRoutes(router)
return (
<div>
// 渲染路由
{ outlet }
</div>
)
}
在router/index.jsx
中,使用lazy
即可实现路由的懒加载
import { lazy } from 'react'
const Home = lazy(() => import('../views/Home/Home'))
但是通常情况下,lazy
和Suspense
是结合使用的,不然会出现报错(但是我目前写lazy没有出现报错),可以通过如下代码书写我们的懒加载路由:
import { lazy, Suspense } from 'react'
import { Navigate } from 'react-router-dom'
const Home = lazy(() => import('../views/Home/Home'))
const About = lazy(() => import('../views/About/About'))
const routes = [
{
path: '/',
element: <Navigate to="/home" />
},
{
path: '/home',
element: (
// 当路由很多时,书写这段代码会显得非常繁琐,可以封装一个withLoadingComponent方法
<Suspense fallback={<div>loading...</div>}>
<Home />
</Suspense>
)
},
{
path: '/about',
element: withLoadingComponent(<About />),
}
]
// comp的ts类型为JSX.Element
const withLoadingComponent = (comp) => {
return (
<Suspense fallback={<div>loading...</div>}>
{ comp }
</Suspense>
)
}
export default routes
路由表通过children
属性可以书写子路由,但是子路由并不会直接展示在页面上,需要通过在展示子路由的页面上放置路由出口
路由表配置:
const routes = [
{
path: '/',
element: <Navigate to="/home" />
},
{
path: '/home',
element: <Home />
},
// 该写法需要在About组件写路由出口
{
path: '/about',
element: <About />,
// 子路由
children: [
{
// 子路由:可以写全路径
path: '/about/news',
element: <News />
},
{
// 子路由:写二级路径,但不要前面的'\'
path: 'message',
element: <Message />
},
{
// 子路由:默认展示Hello组件
index: true,
element: <Hello />
}
]
},
// 直接是路由出口,就不需要在组件内写
{
path: '/layout',
element: <Outlet />,
// 子路由
children: [
{
path: '/layout/news',
element: <News />
}
]
}
]
路由出口:(在About组件内)
import React from 'react'
import { Outlet } from 'react-router-dom'
export default function about() {
return (
<div>
about
<Outlet />
</div>
)
}
router/index.jsx
中:使用createBrowserRouter
创建BrowserRouter
路由:
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../views/layout'
import Login from '../views/login'
const router = createBrowserRouter([
{
path: '/',
element: <Layout />
},
{
path: '/login',
element: <Login />
}
])
export default router
main.jsx
中:引入内置组件RouterProvider
,替换用BrowserRouter
包裹App
组件的写法
import React from 'react'
import ReactDOM from 'react-dom/client'
// 引入RouterProvider组件
import { RouterProvider } from 'react-router-dom'
import router from './router/index.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<RouterProvider router={router} />
)
注册路由是三、3
使用内置组件的方法
router/index.jsx
中:通过组件的children属性,将需要展示的组件包裹在封装的路由守卫组件中。import { createBrowserRouter } from 'react-router-dom'
import Layout from '../views/layout'
import Login from '../views/login'
// 路由守卫组件
import AuthRouter from './AuthRouter'
const router = createBrowserRouter([
{
path: '/',
// 因为组件中的内容会通过props的children属性传递给子组件
element: <AuthRouter><Layout /></AuthRouter>
},
{
path: '/login',
element: <Login />
}
])
export default router
AuthRouter.jsx
中:判断是否有token,有则加载Layout
组件内容,没有则跳转到Login
页面// 封装高阶组件 路由鉴权组件
import { Navigate } from "react-router-dom"
const AuthRouter = ({ children }) => {
const token = localStorage.getItem('token')
if (token) {
return <>{ children }</>
} else {
return <Navigate to='/login' replace />
}
}
export default AuthRouter
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
App.jsx
import { useRoutes, useLocation, useNavigate, Navigate } from 'react-router-dom'
import { useEffect } from 'react'
import Layout from './views/layout'
import Login from './views/login'
const router = [
{
path: '/',
element: <Layout />
},
{
path: '/login',
element: <Login />
}
]
const AuthRouter = () => {
const outlet = useRoutes(router)
const location = useLocation()
let token = localStorage.getItem('token')
if (location.pathname !== '/login' && !token) {
// 这里不能直接使用useNavigate进行跳转,因为需要的是一个jsx组件
return <ToLogin />
} else {
return outlet
}
}
const ToLogin = () => {
// 写法1:可以直接用Navigate组件
// return
// 写法2:编程式路由跳转
const navigateTo = useNavigate()
useEffect(() => {
navigateTo('/login')
}, [])
return <></>
}
export default function App() {
return (
<>
<AuthRouter />
</>
)
}
思路1和思路2的核心都是判断是否有token,没有token就跳转登陆页,区别是用编程式导航调整和用组件进行跳转。