文章目录
React-Router
前端路由是如何做到URL和内容进行映射呢?怎么原生的监听URL的改变。
URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性;
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;
hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。
hashchange事件触发时,事件对象会有hash改变前的URL(oldURL)和hash改变后的URL(newURL)两个属性:
window.addEventListener('hashchange',function(e) { console.log(e.oldURL); console.log(e.newURL) },false);
HTML5的History
◼ history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
replaceState:替换原来的路径;
pushState:使用新的路径;
popState:路径的回退;
go:向前或向后改变路径;
forward:向前改变路径;
back:向后改变路径;
popstate 事件是通过 window.addEventListener('popstate')
进行注册的。但触发条件需要满足下面两点:
点击浏览器的【前进】【后退】按钮,或者调用 history 对象的 back、forward、go 方法
之前调用过 history 对象的 replaceState 或 pushState 方法
Router的基本使用
◼ 安装React Router:
npm install react-router-dom
◼ react-router最主要的API是给我们提供的一些组件:
◼ BrowserRouter或HashRouter
Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;
BrowserRouter使用history模式;
HashRouter使用hash模式;
-src
—index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {
Provider } from 'react-redux';
import store from './store';
import {
HashRouter,BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<HashRouter>
<Provider store={
store}>
<App />
</Provider>
</HashRouter>
// </React.StrictMode>
);
路由映射配置
◼ Routes:包裹所有的Route,在其中匹配一个路由
Router5.x使用的是Switch组件
◼ Route:Route用于路径的匹配;
path属性:用于设置匹配到的路径;
element属性:设置匹配到路径后,渲染的组件;
✓ Router5.x使用的是component属性
exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;
✓ Router6.x不再支持该属性
-src
—App.jsx
import {
Routes,
Route
} from "react-router-dom";
export class App extends PureComponent {
render() {
<Routes>
<Route path="/home" element={<Home />}>
</Routes>
}
}
路由的嵌套
◼ 在开发中,路由之间是存在嵌套关系的。
◼ 组件用于在父路由元素中作为子路由的占位元素。
-src
—pages
-------App.jsx
<Routes>
<Route path="/home" element={<Home />}>
<Route path="/home/homeChild" element={<HomeChild />}></Route>
</Route>
</Routes>
-src
—pages
-------Home.jsx
子路由的出口
组件用于在父路由元素中作为子路由的占位元素。
这样HomeChild组件就会被渲染到Home组件的占位元素的位置
export class Home extends PureComponent {
render() {
<div>
<h2>Home Page</h2>
<div>
{/* 占位组件 */}
<Outlet />
</div>
</div>
}
}
路由配置和跳转
Link和NavLink:
通常路径的跳转是使用Link组件,最终会被渲染成a元素;
NavLink是在Link基础之上增加了一些样式属性,默认叫active的className,通过配置css可以让选中的标签展现active类名下的样式;
to属性:Link中最重要的属性,用于设置跳转到的路径;
-src
—App.jsx
import {
Routes,
Route
} from "react-router-dom";
import './style.css'
export class App extends PureComponent {
render() {
<div>
<nav>
<Link to="/home">首页</Link>
<NavLink to="/home">首页</NavLink>
</nav>
<Routes>
<Route path="/home" element={<Home />}>
</Routes>
</div>
}
}
-src
—style.css
nav .active {
color: red;
font-size: 18px;
}
手动路由的跳转
◼ 实际上我们也可以通过JavaScript代码进行跳转。
我们知道Navigate组件是可以进行路由的跳转的,但是依然是组件的方式。
如果我们希望通过JavaScript代码逻辑进行跳转(比如点击了一个button),那么就需要获取到navigate对象。
◼ 在Router6.x版本之后,代码类的API都迁移到了hooks的写法:
如果我们希望进行代码跳转,需要通过useNavigate的Hook获取到navigate对象进行操作;
例子:
import {
useNavigate,
} from "react-router-dom";
export function RouterHook() {
const navigate = useNavigate()
return(
<div>
<button onClick={e => navigate('/home')}>去home</button>
</div>
)
}
那么如果是一个函数式组件,我们可以直接调用,但是如果是一个类组件呢?
类组件我们可以使用一个高阶组件包裹,在返回的组件的props中写入react-router-dom的方法,这里封装了useLocation, useNavigate, useParams, useSearchParams四个钩子
useParams和useSearchParams钩子:用来接收路由传参
useLocation钩子:监听路由的地址
useNavigate钩子:实现手动路由跳转
案例:
-src
—hoc
-------withRouter.js
import {
useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
function withRouter(WrapperComponent) {
return function (props) {
// 1.导航
const navigate = useNavigate()
// 2.动态路由的参数: /detail/:id
const params = useParams()
// 3.查询字符串的参数: /user?name=why&age=18
const [searchParams] = useSearchParams()
const query = Object.fromEntries(searchParams)
// 4.路由地址
const location = useLocation()
const router = {
navigate, params, location, query }
return (
<WrapperComponent
{
...props}
router={
router}
></WrapperComponent>
);
};
}
export default withRouter
再到类组件中使用withRouter高阶组件包裹需要使用router hook的组件上
import withRouter from "../hoc/withRouter";
export class Home extends PureComponent {
....
}
export default withRouter(Home);
路由参数传递
◼ 传递参数有二种方式:
动态路由的方式;
search传递参数;
◼ 动态路由的概念指的是路由中的路径并不会固定:
比如/detail的path对应一个组件Detail;
如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;
这个匹配规则,我们就称之为动态路由;
通常情况下,使用动态路由可以为路由传递参数。
路由传递参数
import {
Link,
Routes,
Route,
} from "react-router-dom";
export function App(props) {
return (
<div>
<Link to="/home/detail/123">给detail页面传参123</Link>
<Routes>
<Route path="/home/detail/:id" element={<Detail />}></Route>
</Routes>
</div>
)
}
在组件中接收参数
import React, { PureComponent } from 'react'
import { withRouter } from '../hoc'
export class Detail extends PureComponent {
render() {
const { router } = this.props
const { params } = router
return (
<div>
<h1>Detail Page</h1>
<h2>id: {params.id}</h2>
</div>
)
}
}
export default withRouter(Detail)
◼ search传递参数
给Contexta组件路由传参
import {
Link,
Routes,
Route,
} from "react-router-dom";
export function App(props) {
return (
<div>
<Link to="/home/context?age=18&name=顽皮宝">给contexta页面传参</Link>
<Routes>
<Route path="/home/context" element={<Contexta />}></Route>
</Routes>
</div>
)
}
在组件中接收参数
import React, { PureComponent } from 'react'
import { withRouter } from '../hoc'
export class Contexta extends PureComponent {
render() {
const { router } = this.props
const { query } = router
return (
<div>
<h1>User: {query.name}-{query.age}</h1>
</div>
)
}
}
export default withRouter(Contexta)
Navigate导航
◼ Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
j例子:在匹配到’/'的时候,直接跳转到/home页面
<Route path="/" element={<Navigate to="/home" />}></Route>
Not Found页面配置
◼ 如果用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置将什么内容都不显示。
◼ 很多时候,我们希望在这种情况下,让用户看到一个Not Found的页面。
开发一个Not Found页面;
配置对应的Route,并且设置path为*即可;
<Route path="*" element={<NotFound />}></Route>
路由的配置文件
◼ 目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的。
◼ 但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理:
在早期的时候,Router并且没有提供相关的API,我们需要借助于react-router-config完成;
在Router6.x中,为我们提供了useRoutes API可以完成相关的配置;
◼ 如果我们对某些组件进行了异步加载(懒加载),那么需要使用Suspense进行包裹
-src
—router
-------index.js
import Home from '../pages/Home'
import HomeRecommend from "../pages/HomeRecommend"
// import About from "../pages/About"
// import Login from "../pages/Login"
import {
Navigate } from 'react-router-dom'
import React from 'react'
// 懒加载
const About = React.lazy(() => import("../pages/About"))
const Login = React.lazy(() => import("../pages/Login"))
const routes = [
{
path: "/",
element: <Navigate to="/home"/>
},
{
path: "/home",
element: <Home/>,
children: [
{
path: "/home/recommend",
element: <HomeRecommend/>
},
]
},
{
path: "/about",
element: <About/>
},
{
path: "/login",
element: <Login/>
},
]
export default routes
-src
—App.jsx
import React from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
export function App(props) {
return (
<div className='app'>
<div className='nav'>
<Link to="/home">首页</Link>
<Link to="/login">登录</Link>
</div>
<div className='content'>
{useRoutes(routes)}
</div>
</div>
)
}
export default App