十四、react路由

一、路由的使用

React Router官网:https://reactrouter.com/

安装

1 npm i -S react-router-dom

1、相关组件

  • Router组件:包裹整个应用(单个具体的组件/根组件),一个React应用只需要使用一次
    • Router类型: HashRouter和BrowserRouter
      • HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
      • BrowserRouter:使用H5的history API实现(localhost3000/first)
    • HashRouter和BrowserRouter的不同点:
      • HashRouter使用URL的哈希值实现,BrowserRouter使用H5的history API实现
      • HashRouter路径中有#,BrowserRouter没有#
      • HashRouter刷新后会导致路由state参数的丢失,BrowserRouter不会
  • Link/NavLink组件:用于指定导航链接(a标签),就是做声明式导航的
    • 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
    • NavLink可以实现路由链接的高亮显示,通过属性activeClassName指定样式名
  • Route组件:指定路由展示组件相关信息(组件渲染)
    • path属性:路由规则,这里需要跟Link组件里面to属性的值一致
    • component属性:展示的组件

注意:LinkRoute组件必须被Router组件给包裹,否则报错。

2、封装NavLink组件

1、NavLink标签是一个高亮显示的导航标签

2、NavLink标签体内容是一个特殊的标签属性,通过this.props.children属性可以获取标签体内容(即Home或About)

NavLink封装

 1 import React,{Component} from 'react'
 2 import {NavLink} from 'react-router-dom'
 3 
 4 export default class MyNavLink extends Component{
 5   render(){
 6     return(
 7       <NavLink activeClassName="link-name" className="link-item" {...this.props}/>
 8     )
 9   }
10 }

使用

1 <MyNavLink to="/home">Home</MyNavLink>
2 <MyNavLink to="/about">About</MyNavLink>

3、Switch组件

Switch组件,让其去包裹路由的Route组件(Switch组件保证只渲染其中一个子路由)

1 <Switch>
2   {/*Switch只渲染第一个匹配的组件*/}
3   <Route path="/home" component={Home}></Route>
4   <Route path="/home" component={Test}></Route>
5   <Route path="/about" component={About}></Route>
6 </Switch>
二、路由导航方式

1、声明式导航

src/index.js入口文件中定义一个路由模式(Router组件包裹根组件)

 1 import React from "react";
 2 import ReactDOM from "react-dom";
 3 
 4 // 设置路由模式
 5 import {HashRouter as Router} from 'react-router-dom'
 6 
 7 // 定义 provider
 8 import { Provider } from "react-redux";
 9 import store from "./Store/index";
10 
11 import App from "./App";
12 
13 ReactDOM.render(
14   <Provider store={store}>
15       // 使用Router包裹根组件
16     <Router>
17       <App></App>
18     </Router>
19   </Provider>,
20 document.getElementById("root")
21 );

在根组件src/App.jsx中引入路由相关组件(也可以在具体的某个组件中使用Router组件)

 1 import React, { Component } from "react";
 2 import { HashRouter as Router, Route, Link } from "react-router-dom";
 3 
 4 import Cmp10 from "./Components/Cmp10";
 5 import Cmp11 from "./Components/Cmp11";
 6 
 7 class App extends Component {
 8   render() {
 9     return (
10       <Router>
11         <div>
12           <h1>导航区域</h1>
13           <div>
14             <ul>
15               <li>
16                 <Link to="/home">首页</Link>
17               </li>
18               <li>
19                 <Link to="/news">新闻</Link>
20               </li>
21             </ul>
22           </div>
23         </div>
24         <Route path="/home" component={Cmp10}></Route>
25         <Route path="/news" component={Cmp11}></Route>
26       </Router>
27     );
28   }
29 }
30 export default App;

需要注意:

  刨除样式的影响,Route组件在HTML代码中的位置决定了渲染后其在页面中显示的位置。如果Route放在最后,则其显示的时候也在最后;若其放在渲染内容的最前面,相应的显示也会在最开始。

2、编程式导航

路由组件与一般组件有三个重要的不同属性:history、location、match

  • react-router-dom中通过history对象中的push/replace/go等方法实现编程式导航功能,这一点与之前的vue路由还是很相似的。
    • push:先进后出,可以返回查看历史跳转情况
    • replace:替换,不能返回查看历史页面
1 <MyNavLink replace to="/home">Home</MyNavLink>
1 this.props.history.push({
2   pathname: "/home",
3   search: "from=404",    // 表示传递查询字符串
4   state: {                    // 隐式传参,地址栏不体现
5     username: "admin",
6   },
7 });
8 
9 this.props.history.go(-1)

不要在根组件中使用编程式导航。

三、路由匹配

1、模糊匹配与严格匹配

模糊匹配(默认)

1 {/*MyNavLink中的/about/a/b匹配到Route中的/about"*/}
2 <MyNavLink to="/home">Home</MyNavLink>
3 <MyNavLink to="/about/a/b">About</MyNavLink>
4 
5 <Switch>
6   <Route path="/home" component={Home}></Route>
7   <Route path="/home" component={Test}></Route>
8   <Route path="/about" component={About}></Route>
9 </Switch>

严格匹配:exact属性

1 <MyNavLink to="/home">Home</MyNavLink>
2 <MyNavLink to="/about/a/b">About</MyNavLink>
3 
4 <Switch>
5   <Route exact path="/home" component={Home}></Route>
6   <Route exact path="/home" component={Test}></Route>
7   <Route exact path="/about" component={About}></Route>
8 </Switch>

2、重定向路由

React的重定向路由有以下两种写法:

方式一:推荐

1 import { Redirect } from "react-router-dom"
2 
3 <Redirect from="/from" to="/to"></Redirect>
4 <Route path="/to" component={xxxx}></Route>

方式二 :不推荐

1 import { Route, Redirect } from "react-router-dom"
2 
3 <Route path="/from">
4     <Cmp></Cmp>
5     <Redirect to="/to" />
6 </Route>

3、404路由

项目中少不了404页面的配置,在React里面配置404页面需要注意:

1 import NotFound from "./Components/404";
2 
3 <Route>
4     <NotFound></NotFound>
5 </Route>
6 // 或
7  <Route component={NotFound}></Route>
  • 需要用到Switch组件,让其去包裹路由的Route组件(Switch组件保证只渲染其中一个子路由)
  • 在404路由的位置,不需要给定具体的路由匹配规则,不给path表示匹配*,即所有的路由都会匹配,因此用404路由一定要加Switch匹配一个路由。
  • 并不会因为当前是404路由/重定向路由而改变状态码,因为当前写的是前端的内容,状态码是后端提供的,只有等后期上线以后才能有状态码。
 1 <div>
 2   <Link to="/home">家</Link> &emsp;
 3   <Link to="/news">新闻</Link>&emsp;
 4   <Link to="/about">关于</Link>&emsp;
 5   <Redirect from="/" to="/home"></Redirect>
 6   <Switch>
 7     <Route path="/home" component={Cmp11}></Route>
 8     <Route path="/news" component={Cmp12}></Route>
 9     <Route path="/about" component={Cmp13}></Route>
10     <Route component={NotFound}></Route>
11   </Switch>
12 </div>
四、嵌套路由

在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。

例如,路由规则如下

1 admin/user
2 admin/goods

它们路由前缀的admin是相同的,不同的只是后面一部份。

实现方式

  • 先定义个组件,用于负责匹配同一前缀的路由,将匹配到的路由指向到具体的模块
1 <Route path="/admin" component={Admin}></Route>
  • 创建模块路由组件负责指定各个路由的去向
 1 render() {
 2   // 获取前缀,供后续地址做路由拼接
 3   let prefix = this.props.match.path;
 4   return (
 5     <div>
 6       <h1>欢迎使用后台管理程序</h1>
 7       <Route path={`${prefix}/user`} component={User}></Route>
 8       <Route path={`${prefix}/goods`} component={Goods}></Route>
 9     </div>
10   );
11 }
五、路由懒加载

优化性能,让路由按需加载

  • 引入react上的Suspense,包裹注册的路由组件
  • Suspense用于展示路由未加载出来展示的组件
 1 import React, {Component, lazy, Suspense} from 'react'
 2 import {NavLink, Route} from 'react-router-dom'
 3 
 4 // Loading用于懒加载组件未加载出来的展示
 5 import Loading from './Loading'
 6 
 7 // import Home from './Home'
 8 // import News from './News'
 9 
10 const Home = lazy(() => {import('./Home')})
11 const News = lazy(() => {import('./News')})
12 
13 export default class Demo extends Component{
14   <div>
15     <h1>导航区域</h1>
16     <div>
17       <ul>
18         <li>
19           <Link to="/home">首页</Link>
20         </li>
21         <li>
22           <Link to="/news">新闻</Link>
23         </li>
24       </ul>
25     </div>
26     <Suspense fallback={<Loading/>}>
27       <Route path="/home" component={Home}></Route>
28       <Route path="/news" component={News}></Route>
29     </Suspense>
30   </div>
31 }
六、路由参数

路由参数:在Route定义渲染组件时给定动态绑定的参数。

React路由传参方式有三种:

1、动态路由参数(param)

/film/detail/:id形式传递的数据

1 <Link to={`/film/detail/${detailObj.id}`}></Link>

在目标页面路由中传递

1 <Router path="/film/detail/:id" component={Detail}></Router>

在落地组件中通过this.props.match.params得到

2、查询字符串(search,也可叫query)

  • 通过地址栏中的 ?key=value&key=value传递
  • 在落地组件中通过this.props.location.search得到

3、隐式传参(state),通过地址栏是观察不到的

  • 不适合写在声明式导航中,写在编程式导航中更加合适
1 <Link to={{pathname:`/film/detail`,state={id:detailObj.id,title:detailObj.title}}}></Link>
  • 埋点数据
  • 在落地组件中通过this.props.location.state得到

接收示例:

 1 constructor(props){
 2   super(props)
 3   this.state = {
 4     // 接收动态路由参数
 5     news_id: this.props.match.params.id,
 6     // 接收查询字符串并处理
 7     query: querystring.parse(this.props.location.search.slice(1)),
 8     // 接收state
 9     state: this.props.location.state
10   };
11 }
七、路由三种渲染方式

1、component属性(组件对象/函数)

1 <Route path="/home" component={Home} />
1 <Route path="/home" component={() => <Home />} />

2、render属性(函数)

1 <Route path="/home" render={props => <Home />} />

3、children属性(组件/函数

1 <Route path="/about" children={props => {
2     if(props.match){
3         return <div>children渲染</div>
4     }
5 }} />
1 <Route path="/about" children={<About />} />

注意

  • component可以使用组件类渲染或内联方式渲染,render只能使用函数,children使用函数或直接使用组件
  • 当children的值是一个函数时,无论当前地址和path路径匹不匹配,都将会执行children对应的函数,当children的值为一个组件时,当前地址和path不匹配时,路由组件不渲染
  • children函数方式渲染,会在形参中接受到一个对象,对象中match属性如果当前地址匹配成功返回对象,否则null
  • children函数方式渲染,在自定义导航组件的上面会非常好用
八、withRouter高阶组件

作用:把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上

  • 默认情况下,必须是经过路由匹配渲染的组件才存在this.props才拥有路由参数,才能使用编程式导航的写法,才能执行this.props.history.push('/uri')跳转到对应路由的页面
  • 然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
1 // 引入withRouter
2 import { withRouter} from 'react-router-dom'
3 
4 // 执行一下withRouter
5 export default withRouter(Cmp)

该高阶组件是路由包自带的东西,因此只需要引入+使用就可以了,不需要自己定义。

九、自定义导航组件

在项目中往往不是所有的声明式导航都是需要a标签完成,有时候可能需要别的标签

此时如果在需要的地方去写编程式导航就会有代码重复可能性,就在对于公共代码进行提取

思路:

  • 定义一个普通组件可以是类组件也可以是函数式组件
  • 父组件能向子组件传值props
  • 不管路由规则是否匹配都要显示组件Route,children渲染方式(函数式)
  • 注意点:react中组件是大写字母开头,html标签也是组件

声明式导航参考

 1 import React, { Component, Fragment } from "react";
 2 import { Route } from "react-router-dom"
 3 // 引入自定义导航组件
 4 import MyLink from "./Components/MyLink"
 5 import Cmp1 from "./Components/Cmp1";
 6 import Cmp2 from "./Components/Cmp2";
 7 
 8 class App extends Component {
 9   render() {
10     return (
11       <Fragment>
12         <MyLink tag="h1" to="/cmp1">
13           去1
14         </MyLink>
15         <MyLink tag="h1" to="/cmp2">
16           去2
17         </MyLink>
18         <Route path="/cmp1" component={Cmp1}></Route>
19         <Route path="/cmp2" component={Cmp2}></Route>
20       </Fragment>
21     );
22   }
23 }
24 export default App;

自定义导航组件参考

 1 import React, { Component, Fragment } from "react";
 2 import { withRouter, Route } from "react-router-dom";
 3 
 4 class MyLink extends Component {
 5   // 点击跳转动作
 6   goUrl = () => {
 7     this.props.history.push(this.props.to);
 8   };
 9   render() {
10     // 获取参数
11     var Tag = this.props.tag ? this.props.tag : "a";
12     return (
13       <Fragment>
14         <Route
15           path={this.props.to}
16           children={({ match }) => {
17             if (match) {
18               // 匹配
19               return (
20                 <Tag
21                   onClick={this.goUrl}
22                   style={{ color: "red" }}
23                   >
24                   {this.props.children}
25                 </Tag>
26               );
27             } else {
28               // 不匹配
29               return (
30                 <Tag onClick={this.goUrl}>
31                   {this.props.children}
32                 </Tag>
33               );
34             }
35           }}>
36         </Route>
37       </Fragment>
38     );
39   }
40 }
41 
42 export default withRouter(MyLink);
十、样式引入问题

css样式引入:public文件下自定义样式引入

  • 不要写相对路径,例如./css/index.css写成/css/index.css
  • 路径写成href="%PUBLIC_URL%/css/index.css"
  • 导航方式:BrowserRouter改成HashRouter