React18 (六) Router5
Router5安装:
npm
npm install react-router-dom@5 -S
yarn
yarn add react-router-dom@5
App.js
import './App.css'; import React, { useReducer, useState } from 'react' import CartContext from './store/CartContext'; import { Route } from 'react-router-dom'; import Home from './component/Home/Home'; import Shopping from './component/Shopping/Shopping'; import Member from './component/Member/Member'; import Bonus from './component/Bonus/Bonus'; import BottomNavigation from '@mui/material/BottomNavigation'; import BottomNavigationAction from '@mui/material/BottomNavigationAction'; import RestoreIcon from '@mui/icons-material/Restore'; import FavoriteIcon from '@mui/icons-material/Favorite'; import ArchiveIcon from '@mui/icons-material/Archive'; import Paper from '@mui/material/Paper'; import { Link, Redirect, Switch, useHistory, useLocation } from 'react-router-dom/cjs/react-router-dom.min'; import Wechat from './component/Bonus/Wechat/Wechat'; const cartsReducer = (params, action) => { const newCarts = { ...params }; switch (action.type) { case 'ADD': if (newCarts.items.indexOf(action.meal) === -1) { newCarts.items.push(action.meal); action.meal.attributes.amount = 1; } else { action.meal.attributes.amount += 1; } newCarts.totalAmount += 1; newCarts.totalPrice += action.meal.attributes.price; return newCarts; case 'REMOVE': if (--action.meal.attributes.amount <= 0) { newCarts.items.splice(newCarts.items.indexOf(action.meal), 1); } newCarts.totalAmount -= 1; newCarts.totalPrice -= action.meal.attributes.price; return newCarts; case 'CLEAR': newCarts.items.forEach(item => delete item.attributes.amount); newCarts.items = []; newCarts.totalAmount = 0; newCarts.totalPrice = 0; return newCarts; default: return params; } } function App() { const [carts, cartsDispatch] = useReducer(cartsReducer, { items: [], totalAmount: 0, totalPrice: 0 }); const [value, setValue] = useState('/'); const _history = useHistory(); const gotoHander = (event, newValue) => { setValue(newValue); _history.push(newValue); } const homeProps = { name: 'hanbao' } const memberProps = { name: 'member', params: {} } return ( <CartContext.Provider value={{ ...carts, cartsDispatch }}> {/* <Route exact path = '/' component ={Home}/> */} <Route exact path='/' children={<Home {...homeProps} />} /> <Route path='/bonus' component={Bonus} /> <Switch> <Route exact path='/bonus/wechat' component={Wechat} /> </Switch> <Route exact path='/shopping' component={Shopping} /> <Route exact path='/home' component={Home} /> <Route path='/member' render={() => <Member {...memberProps} />} /> <Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={1}> <BottomNavigation showLabels value={value} onChange={gotoHander} > <BottomNavigationAction label="Home" value="/" icon={<RestoreIcon />} /> <BottomNavigationAction label="Bonus Plan" value="/bonus" icon={<FavoriteIcon />} /> <BottomNavigationAction label="Store" value="/shopping" icon={<ArchiveIcon />} /> <BottomNavigationAction label="Member" value={`/member/${homeProps.name}`} icon={<ArchiveIcon />} /> </BottomNavigation> </Paper> </CartContext.Provider> ); } export default App;
1. React-Router-Dom包
react router适用于web和原生项目,我们在web项目中使用,所以需要引入的包是react-router-dom。
2. BrowserRouter组件
和Redux类似,要使得路由生效,需要使用Router组件将App组件包裹起来。这里我们选择的是BrowserRouter,除了BrowserRouter外还有其他的Router,暂时我们只介绍BrowserRouter。
下面样例中,为BrowserRouter起一个别名Router,这样一来我们在切换Router时,只需要修改引用位置,而不需要修改其他代码,像是这样:
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { HashRouter as Router } from 'react-router-dom'; import { Provider } from 'react-redux'; import store from './store/index'; // document.documentElement.style.fontSize = 100 / 750 + 'vw'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <Router> <App /> </Router> </Provider> );
3. Route组件
route组件是路由的映射组件,通过该组件将url地址和React组件进行映射,映射后当url地址变为指定地址时指定的组件就会显示,否则不显示。
<Route exact path='/' children={<Home {...homeProps} />} /> <Route path='/bonus' component={Bonus} />
上例中,路径/
和<Home/>
组件进行了映射,路径/bonus
和<Bonus/>
组件进行了映射。当访问http://localhost:3000/bonus
时,Bonus组件会自动渲染显示,默认是渲染Home组件
Route组件可以设置以下几个属性
path,exact,strict,component,render,children,location,sensitive
3.1 path
用来设置要映射的路径,可以是一个字符串或字符串数组。字符串用来匹配单个路径,数组可以匹配多个路径。看一个数组的例子:
<Route path={["/", "/member"]}> <Member/> </Route>
使用数组映射后,当我们访问数组中的路径时都会使组件挂载。设置路径时也可以在路径中设置参数,比如:/member/:id
其中id就是一个参数,可以动态的传递:id
的值,换句话说/member/1
或/member/2
,都会触发组件挂载。
设置动态参数后,在组件的内部可以使用useParams()
钩子来读取参数:
const Member = () => { const {id} = useParams(); return <div>userId:{id}</div> }; ...略... <Route path="/member/:id"> <Member/> </Route> ...略...
3.2 exact
路由的匹配默认并不是完整匹配,这意味着路由映射的地址是/home
时,只要我们访问的路径是以/home
开头就会触发组件的挂载,默认情况下是不会检查路由的子路径的。比如:/home/123
、/home/456
等都会导致home组件挂载。
exact属性用来设置路由地址是否完整匹配,它需要一个布尔值,默认为false,就像上边的情况。如果设置为true,那么只有地址和path完全一致时,组件才会挂载。
<Route path="/home" exact> <Home/> </Route>
3.3 strict
布尔值,默认值为false。false时,会匹配到以/
结尾的路径。比如:path设置为/home
默认情况下/home/
也会导致组件挂载。设置为true时,以/
结尾的路径不会被匹配。
3.4 component
设置路径匹配后需要挂载的组件。作用和Route的标签体类似。
<Route path="/home" component={Home}/>
和标签体指定组件不同,如果通过component属性指定组件,React Router会自动向组件中传递三个参数match、location和history。
3.4.1 match
对象,表示请求匹配的路径信息,其中包含四个属性:
param —— 请求参数
isExact —— 布尔值,请求路径是否完整匹配
path —— 请求路径的规则
url —— 匹配到的url地址
3.4.2 location
对象,表示浏览器地址栏的信息,请求完整路径、查询字符串等,可能具有的属性:
pathname —— 请求的路径
search —— 查询字符串
hash —— hash字符串
state —— 历史记录中的状态对象,可以用来在跳转时传递数据
3.4.3 history
对象,用来读取和操作浏览器的历史记录(页面跳转)等功能,属性:
length —— 历史记录的数量
action —— 当前历史记录的状态,pop(前进、后退、新记录创建、索引发生变化);push(新记录添加);replace(历史记录被替换)
location —— location对象
push() —— 添加新的历史记录
replace() —— 替换历史记录
go() —— 跳转到指定记录
goBack() —— 回退
goForward() —— 前进
block() —— 用来阻止用户跳转行为,可以用Prompt组件代替
3.5 render
render也是Route组件中的属性,和component类似,也用来指定路径匹配后需要挂载的组件。只是render需要的是一个回调函数作为参数,组件挂载时,render对应的回调函数会被调用,且函数的返回值会成为被挂载的组件。render的回调函数中会接收到一个对象作为参数,对象中包含三个属性,即match、location和history,我们可以根据需要选择是否将其传递给组件。
<Route path="/member/:id" render={routeProps => <Member {...routeProps}/>} />
3.6 children
children实际上就是组件的组件体,设置方式有两种一个是通过组件体设置,一个是通过children属性设置。它的值也有两种方式,一种直接传递组件,这样当路径匹配时组件会自动挂载。一种是传递一个回调函数,这样它和render的特点是一样的。
直接设置组件:
<Route path="/mrmber/:id" children={<Member/>} />
<Route path="/member/:id"> <Member/> </Route>
传递回调函数:
<Route path="/member/:id" children={routeProps => <Member {...routeProps}/>} />
<Route path="/member/:id"> {routeProps => <Member {...routeProps}/>} </Route>
需要注意的时,当children接收到的是一个回调函数时,即使路径没有匹配组件也会被挂载到页面中(没有使用Switch标签的情况下),这一特性可以在一些特殊应用场景下发挥作用。如果不希望出现路径不匹配时组件被挂载的情况,最好选择使用render来代替。
4. Switch组件
Switch组件是Route组件的外部容器,可以将Route组件放入到Switch组件中。放入Switch组件中后,匹配路径时会自动自上向下对Route进行匹配,如果匹配到则挂载组件,并且一个Switch中只会有一个Route被挂载。如果将Route组件单独使用,那么所有的路径匹配的Route中的组件都会被挂载。
5. Link组件
Link组件作用类似于a标签(超链接),并且Link组件在浏览器中也会被渲染为超链接。但是Link组件生成的链接点击后只会修改浏览器地址栏的url,并不会真的向服务器发送请求。这种方式有利于组件的渲染,所以在开发中应该使用Link组件而不是超链接。
其他组件
1. HashRouter组件
当我们使用BrowserRouter时,路径会直接根据url地址进行跳转,也就是我们在使用应用时在浏览器的地址栏看到的地址就和我们正常去访问网页一样。但是,HashRouter不是这样,使用HashRouter时,组件的跳转不再是以完整的url形式,而是通过url地址中的hash值进行跳转(url地址中#后的内容为hash值)。
BrowserRouter的地址栏
http://localhost:3000/home
HashRouter的地址栏
http://localhost:3000/#/home
为什么会有这两种Router呢?首先,我们的项目在开发完成后需要进行构建,构建后的代码需要放到服务器中供用户访问。服务器无非就是Nginx,Apache或者NodeJS这些东西,服务器的主要功能是将url地址和网页进行映射。传统web项目中,每一个页面都对应一个文件,当用户访问/index.html时,服务器会自动返回根目录下的index.html。当用户访问/home.html时,服务器会返回根目录下home.html。换句话说url和文件的映射都是由服务器来完成的。但是React项目不同,React项目所有的页面都是通过React进行渲染构建的。项目中只存在一个index.html没有那么多的页面(所以才叫单页应用)。当浏览器地址发生变化时,比如用户访问/about时,此时是不需要服务器介入的,react router会自动挂载对应的组件。当我们将React项目部署到服务器时,如果直接访问根目录,请求会直接发送给index.html。这个页面我们是有的,所以此时不会有任何问题。用户访问页面后,点击页面后的连接切换到不同的组件也没有问题,因为页面并没有真的发生跳转,而是通过react router在内存中完成了模拟跳转。但是,当我们刷新某个路由或直接通过浏览器地址栏访问某个路由时,比如:http://localhost:3000/home,此时请求会发送给服务器,服务器会寻找名为home的资源(此时并没有经过React)。显然找不到这个资源,于是返回404。
怎么办呢?使用HashRouter,HashRouter通过hash地址跳转,而服务器不会处理hash地址,这样地址就会交由React处理,路由便可正常跳转。缺点是url地址上总会多出一个#,但不妨碍使用。
2. NavLink组件
特殊版本的Link,可以根据不同的情况设置不同的样式。属性:
activeClassName —— 字符串 链接激活时的class
activeStyle —— 对象 链接激活时的样式
isActive —— 函数,可动态判断链接是否激活
style —— 函数,动态设置样式
className —— 函数,动态设置class值
3. Prompt组件
prompt组件可以在用户离开页面前弹出提示。属性:
message -- 字符串/函数,设置离开前显示的提示信息 when -- 布尔值,设置是否显示提示
4. Redirect组件
将请求重定向到一个新的位置,经常用来进行权限的处理。例如:当用户已经登录时则正常显示组件,用户没有登录时则跳转到登录页面。
<Route {...rest} render={({ location }) => isLogged ? ( children ) : ( <Redirect to={{ pathname: "/login-out", state: { from: location } }} /> ) } />
上例中,如果isLogged的值为true,表示用户已经登录,若用户登录,则挂载对应组件。若isLogged值为false,则挂载Redirect组件触发重定向,重定向会使得路径跳转到/login-out页面。属性:
to —— 重定向的目标地址,可以是一个字符串也可以是一个对象
from —— 需要重定向的地址
push —— 布尔值,是否使用push方式对请求进行重定向
5. 钩子函数
5.1 useHistory
useHistory钩子使您可以访问可用于导航的history实例。
5.2 useLocation
useLocation钩子返回代表当前 URL 的location对象。你可以把它想象成一个 useState,只要 URL 发生变化,它就会返回一个新的location。
5.3 useParams
useParams返回URL参数的键/值对对象。使用它来访问当前 <Route> 的 match.params。
5.4 useRouteMatch
useRouteMatch 钩子尝试以与 <Route> 相同的方式匹配当前 URL。它主要用于在不实际渲染 <Route> 的情况下访问匹配数据。