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 (

logo

React Router V5

  • Home
  • About

); }

function Home() { return (

Home

); }

function About() { return (

About

); }

export default App;

  1. ![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)
  2. - HashRouter底层基于 hash实现<br />由于HashRouter会在路径上加#/ , 而URL中#之后的部分是不会发送给服务器的,对于服务器而言,路径依旧是localhost://3000,服务器只需要返回一份HTML就可以,路由切换在浏览器端完成。
  3. - Browser [history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 实现<br />/对应Home页面 /about对应About页面,但是这样的设计需要服务器端渲染。每次更改路由时,会像服务器发送请求,如果服务器未配置对应路径的文件,就会出现404情况, (对于初始化页面,即路由为/时,不会发送请求) 使用BrowserRouter需要再加一层服务器配置(node/nginx),让前端发送的请求映射到对应的html文件上.
  4. [
  5. ](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等信息 { return ; }} /> { console.log(routeProps); const { history } = routeProps; history.push(“/about”, { data: {name: “Joy”,age:18}, query:’from’ }); }} />

![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;

image.png
不加Switch的情况下,发现点击about的时候,同时渲染了Home和AnyComponent,原因:不加Switch情况下,路径为/about ,首先匹配到的是Home对应的path:’/‘,然后匹配到About组件对应的’/about’, 同时也匹配到了’/:id,所以依次会把三个组件都渲染出来。

这时候,Switch上场了,它的作用是:会从包裹的子组件中找到匹配的第一个组件并返回。这里注意下:言外之意不会渲染多个组件了
image.png
我们加了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,这些属性可以通过useHistoryuseLocationuseRouteMatchuseParams来获取。

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>
  );
}

image.png这里额外说下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等信息注入组件中。
两种方式:

  1. withRouter(Component)
  2. @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

ok啦~!
image.png

路由懒加载

三种方式 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
    }
});

未懒加载
image.png
懒加载
image.png
可以看到 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等路由同样被单独打包出来了!
image.png