V5主要API概览
针对Router,大概有3类组件:
- 容器routers:BrowserRouter 和HashRouter
- 匹配组件:Swicth和Route
- 导航组件:Link和NavLink以及Redirect
除了这三种组件,本文章还会针对路由高阶玩儿进行demo示例展示
- 高阶路由withRouter
- react-router-config
- 路由懒加载
容器路由Router:HashRouter vs BrowserRouter
HashRouter和BrowserRouter在使用上是没有什么区别的,都是包裹整个路由应用。
区别是:url路径的展示不一样 HashRouter的url上会有# ```javascript import React from “react”; import { BrowserRouter, HashRouter, Switch, Route, Link, } from “react-router-dom”; import “./App.css”; import logo from “./logo.svg”;
//Demo1: HashRouter & BrowserRouter
function App() { return (
React Router V5
- Home
- About
function Home() { return (
Home
function About() { return (
About
export default App;
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1637573329542-5b6350a6-751f-4fd8-9fb5-877329885e04.png#clientId=u14b74c98-dad3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1073&id=u45c8cddd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2146&originWidth=2114&originalType=binary&ratio=1&rotation=0&showTitle=false&size=373250&status=done&style=none&taskId=uabde91f8-c5ec-41fd-a60b-1ef98adf0e0&title=&width=1057)
- HashRouter底层基于 hash实现<br />由于HashRouter会在路径上加#/ , 而URL中#之后的部分是不会发送给服务器的,对于服务器而言,路径依旧是localhost://3000,服务器只需要返回一份HTML就可以,路由切换在浏览器端完成。
- Browser [history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 实现<br />/对应Home页面 /about对应About页面,但是这样的设计需要服务器端渲染。每次更改路由时,会像服务器发送请求,如果服务器未配置对应路径的文件,就会出现404情况, (对于初始化页面,即路由为/时,不会发送请求) 使用BrowserRouter需要再加一层服务器配置(node/nginx),让前端发送的请求映射到对应的html文件上.
[
](https://v5.reactrouter.com/web/api/BrowserRouter)<br />** **将HashRouter更改成BrowserRouter 同时加**属性basename,可以看到 **通过Link或者history跳转的路径前自动加上v2
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1637574505469-49d88c95-2f4e-4734-a6fe-4f652788f862.png#clientId=u14b74c98-dad3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=989&id=u01db817b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1978&originWidth=2160&originalType=binary&ratio=1&rotation=0&showTitle=false&size=331158&status=done&style=none&taskId=u64775f18-3b20-4b20-9a58-5970a81367d&title=&width=1080)
点击查看 [BrowserRouter和HashRouter属性](https://v5.reactrouter.com/web/api/BrowserRouter)
<a name="N87Fq"></a>
## 匹配Route:Switch和Route
- **Switch:**搜索Switch包裹的Route ,找到和当前路径匹配的Route,**渲染对应的组件忽略其他Route**,意味着我们应该详尽的设置Route的 path,如果没有找到匹配的路径,render null。
- **Route :**路径从头开始匹配,不是全部匹配,所以`<Route path="/">` 将会匹配到所有的url,我们通常放置到最后,或者设置exact,`<Route exact path="/">` 匹配完整路径
<a name="ptgrJ"></a>
#### Demo1: Route有3种方式来渲染匹配组件
// 1. 支持子组件形式
// 2. 支持component形式
// 3. 支持render形式,render中参数可以获取到history、match、location等信息
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1637575469948-1e438e83-155a-422e-abe3-9b70730eb909.png#clientId=u14b74c98-dad3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1603&id=u8d35fec3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=3206&originWidth=2130&originalType=binary&ratio=1&rotation=0&showTitle=false&size=603311&status=done&style=none&taskId=ue5718d6c-0071-49ae-932b-0964945a7fe&title=&width=1065)<br />可以看到,通过render形式可以拿到对应的history等信息,可以正常调用history.push等方法
<a name="zjCx5"></a>
#### Demo2:Switch和Route匹配属性
先看下不加Switch情况
```javascript
import React from "react";
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
import "./App.css";
import logo from "./logo.svg";
//demo2: Switch & Route
function App() {
return (
<div className="App">
<BrowserRouter basename="v2">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>React Router V5</p>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
<hr />
{/* <Switch> */}
<Route path="/">
<Home />
</Route>
<Route path="/about" component={About} />
<Route
path="/about/"
exact
strict
render={() => <h2>Render About ID</h2>}
/>
<Route
exact
path="/:id"
render={(routeProps) => {
return <AnyComponent {...routeProps} />;
}}
/>
<Route path="/dashboard">
<Dashboard />
</Route>
{/* </Switch> */}
</div>
</header>
</BrowserRouter>
</div>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
function AnyComponent() {
return (
<div>
<h2>AnyComponent</h2>
</div>
);
}
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
</div>
);
}
export default App;
不加Switch的情况下,发现点击about的时候,同时渲染了Home和AnyComponent,原因:不加Switch情况下,路径为/about ,首先匹配到的是Home对应的path:’/‘,然后匹配到About组件对应的’/about’, 同时也匹配到了’/:id,所以依次会把三个组件都渲染出来。
这时候,Switch上场了,它的作用是:会从包裹的子组件中找到匹配的第一个组件并返回。这里注意下:言外之意不会渲染多个组件了
我们加了Swithc之后发现,确实没有渲染多个组件了,但是它总是匹配到/的Home组件,导致固定渲染Home组件了。
这种情况我们改怎么解?把/的Route放到最后,保证最后才渲染,发现是可以的,但是这样貌似有点不方便。
另外一种加上exact属性,ok,解决了!
<Route path="/" exact>
<Home />
</Route>
关于匹配限制,Route有两个属性可以设置,二者区别:
exact:url和path必须完全一致(不匹配尾部斜杠),但是如果有斜杠也是可以匹配上的 /about /about/
strict:针对斜杠,如果为true,url和path后面的斜杠必须一致(不匹配斜杠后的路径)
二者结合,可以保证完全匹配
// url为 /about 全部渲染
<Route path="/about" component={About} />
<Route
path="/about/"
exact
render={() => <h2>Render About ID</h2>}
/>
// url为 /about 没有渲染
<Route path="/about/" component={About} strict />
//url 为 /about/123 都渲染了
<Route path="/about/" component={About} strict />
<Route path="/about/:id" render={() => <h2>Render About ID</h2>} />
//url 为 /about/123 仅仅返回Render About ID
<Route path="/about/" component={About} strict exact />
<Route path="/about/:id" render={() => <h2>Render About ID</h2>} />
关于Route所有属性,点击查看
导航组件:Link、NavLink、Redirect
本质上最后渲染成a标签,NavLink是一种特殊的Link,当匹配到当前的NavLink时,渲染activeClassName对应的样式
Redirect是重定向
<NavLink to="/react" activeClassName="hurray">
React
</NavLink>
// When the URL is /react, this renders:
// <a href="/react" className="hurray">React</a>
// When it's something else:
// <a href="/react">React</a>
<Redirect from="/request" to="/caigou/request" />
React Router Hooks
Router顶层有history,location,match等属性,match中包含params,这些属性可以通过useHistory,useLocation,useRouteMatch,useParams来获取。
import React from 'react';
import { useHistory,useLocation ,useRouteMatch,useParams} from "react-router-dom";
<Route
exact
path="/any/:id"
render={(routeProps) => {
return <AnyComponent {...routeProps} />;
}}
/>
function AnyComponent() {
let history = useHistory();
let location = useLocation();
let { id } = useParams();
let match = useRouteMatch("/any/:id");
console.log("About页面获取 history ->", history);
console.log("About页面获取 location ->", location);
console.log("About页面获取 id ->", id);
console.log("About页面获取 match ->", match);
return (
<div>
<h2>AnyComponent</h2>
</div>
);
}
这里额外说下useRouteMatch,接受一个path字符串作为参数。当参数的path与当前的路径相匹配时,useRouteMatch会返回match对象,否则返回null
如果useRouteMatch没有传参数,返回的path和url就是当前的url,isMatch返回的就是false。
useRouteMatch在对于一些,不是路由级别的组件。但是组件自身的显隐却和当前路径相关的组件时,非常有用。
比如,你在做一个后台管理系统时,网页的Header只会在登录页显示,登录完成后不需要显示,这种场景下就可以用到useRouteMatch。
// Header组件只会在匹配`/detail/:id`时出现
const Header = () => {
return (
<Route
path="/detail/:id"
strict
sensitive
render={({ match }) => {
return match && <div>Header</div>
}}
/>
)
}
// Header组件只会在匹配`/detail/:id`时出现
const Header = () => {
// 只有当前路径匹配`/detail/:id`时,match不为null
const match = useRouteMatch('/detail/:id')
return (
match && <div>Header</div>
)
}
withRouter
withRouter是高阶组件,可以传递match,location,history等props给wrapped component
用法
通常只有通过路由匹配的情况下,才可以获取到location等信息,布局组件Header,Footer等获取不到,可以通过withRouter将location,match等信息注入组件中。
两种方式:
- withRouter(Component)
- @withRouter 使用@这种写法的话,需要babel-plugin-transform-decorators-legacy包
执行:npm install babel-plugin-transform-decorators-legacy —save-dev
{
"plugins":[
"transform-decorators-legacy"
]
}
import React,{Component} from 'react'
import {withRouter} from 'react-router-dom' // 引入react-router-dom/react-router中的withRouter
class Header extends Component{
}
export default withRouter(App); // 这里通过WithRouter将路由参数传入props中
=====================
@withRouter
export default class Header extends Component {}
react-router-config
react-router-config用于静态路由配置,属于react-router的一个插件,主要用于集中管理路由配置
安装
npm install react-router-config
定义路由配置 router.js
import Home from "./pages/home";
import About from "./pages/about";
import Dashboard from "./pages/dashborad";
import Detail from "./pages/detail";
const routes = [
{
path: "/",
component: Home,
exact: true,
},
{
path: "/about",
component: About,
//此处添加嵌套路由
routes: [
{
path: "/about/detail",
component: Detail,
},
],
},
{
path: "/dashboard",
component: Dashboard,
},
];
//将路由表数组导出
export default routes;
app.js
import React, { lazy, Suspense } from "react";
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
import "./App.css";
import logo from "./logo.svg";
import { renderRoutes } from "react-router-config";
import routes from "./router";
// demo4: react-router-config
function App() {
return (
<div className="App">
<BrowserRouter basename="v2">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>React Router V5</p>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/about/detail">About嵌套路由</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
<hr />
{renderRoutes(routes)}
</div>
</header>
</BrowserRouter>
</div>
);
}
export default App;
注意嵌套路由需要在对应的父级处理下 about.js
import React from 'react';
import { renderRoutes } from "react-router-config";
function About (props){
return (
<>
<div>This is About Page</div>
{renderRoutes(props.route.routes)}
</>
)
}
export default About
路由懒加载
三种方式 require.ensure / import() /React.lazy()
v2 require.ensure
https://www.html.cn/doc/webpack2/guides/code-splitting-require/
<Route path="courses/:courseId" getComponent={(location, cb) => {
// 做一些异步操作去查找组件
cb(null, {sidebar: CourseSidebar, content: Course})
}}/>
//1. 用require.ensure代码分割
getComponents(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Course'))
})
}
//2. async进行异步加载
{ path: '/AssetSearchWeb', component: async () => AssetSearchWeb },
// 获取异步加载的content区域组件
getComponent = component => (state, callback) => {
component().then(x => {
callback(null, x);
});
};
<Route
key={route.path}
code={route.code}
path={route.path}
getComponent={getComponent(route.component)}
onChange={onRouteChange && onRouteChange}
onEnter={onRouteChange && onRouteEnter}
onLeave={onRouteChange && onRouteLeave}
>
{renderChildrenRoute}
</Route>
routes.forEach((route) => {
route.component = require.ensure([], function () {
var module = require(`./nodes${route.path}/index.jsx`);
return module.__esModule ? module.default : module
});
});
import() v4
function asyncComponent(importComponent) {
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
//////////////////////////////////////////////////////////////
<Route
path="/"
exact
component={asyncComponent(() =>
import(/* webpackChunkName: 'Home'*/ "./pages/home")
)}
/>
<Route
path="/about"
component={asyncComponent(() =>
import(/* webpackChunkName: 'About'*/ "./pages/about")
)}
/>
<Route
path="/dashboard"
component={asyncComponent(() =>
import(/* webpackChunkName: 'DashBoard'*/ "./pages/dashborad")
)}
/>
routes.forEach(route => {
route.component = async () => {
const module = await import(`./nodes${route.path}/index.jsx`);
return module.default ? module.default : module
}
});
未懒加载
懒加载
可以看到 Home About等组件单独从main中打包出来
React.lazy() Reactv16
React16中提供了React.lazy来进行代码分割。
https://zh-hans.reactjs.org/docs/code-splitting.html
https://juejin.cn/post/6844903760028762125
const HomeAsync = lazy(() =>
import(/* webpackChunkName: 'Home'*/ "./pages/home")
);
const AboutAsync = lazy(() =>
import(/* webpackChunkName: 'About'*/ "./pages/about")
);
const DashBoradAsync = lazy(() =>
import(/* webpackChunkName: 'Dashboard'*/ "./pages/dashborad")
);
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" exact component={HomeAsync} />
<Route path="/about" component={AboutAsync}/>
<Route path="/dashboard" component={DashBoradAsync}/>
</Switch>
</Suspense>
可以看到 About Home等路由同样被单独打包出来了!