⑦ React 路由解决方案 react-router

查看专栏其它文章:

① React 介绍及JSX简单使用

② React 面向组件编程(state、props、refs)、事件处理

③ React 条件渲染、组件生命周期、表单与事件

④ React 列表与Keys、虚拟DOM相关说明、AJAX

⑤ React 基于react脚手架构建简单项目

⑥ React 项目中的AJAX请求、组件间通信的2种方式(props、消息订阅和发布)



本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷


因为 React-Router 整体内容繁杂,所以为了文章内容准确性,参考了一些文章。其链接如下:

react 精华之react-router

阮一峰 之 React Router

React Router 中文文档


react-router

react-router 是 React 的一个插件库,它专门用来实现一个 SPA 应用(单页应用)。基于 react 的项目基本都会用到此库。那么何为单页应用?

在一个项目中,通常承载的功能页面是很多的,这就需要我们的页面不断变化。但是页面的变化不能总依靠页面的跳转,随着功能的增加,这必然会影响效率。所以为了解决这个问题单页应用出现了。

单页应用就是 整个应用只有一个完整的页面,点击页面中的链接不会刷新页面,本身也不会向服务器发请求,而是只去做页面的局部更新。

而 React-Router 被称为路由库,何为路由?

路由:一个映射关系 ( key - value ),key 是 路由路径(path),value 是前台路由(组件 component )或后台路由(处理请求的回调函数 function)。

这其中后台路由是 node 服务器端路由,value 是 function。在这里,路由器是 router,它负责管理路由。路由就是 route 。如果要注册路由:router.get(path, function(req, res)),此时当 node 接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据。

前台路由是 浏览器端路由,value 是 component。此时如果要注册路由:<Route path="/about" component={About}>。当请求的是路由 path 时,界面就会更新显示对应的组件,且浏览器端不发送 http 请求。 因此,react-router 专门用来实现 SPA 应用。

现在这么说还是很抽象,先来看一下最基础的用法。(本文不包括 CSS 代码)


基础用法

在使用前先安装:npm install --save react-router-dom (用于 Web 端)

这个 react-router-dom 依赖于 react-router ,所以 react-router 也会被自动安装上。

本例将实现下图中的效果。
在这里插入图片描述
在本例中,App 依然为根组件。在根组件中,将会有两个路由链接,通过点击这两个链接,从而让右侧内容区显示不同路由组件中的内容。

项目结构可按下图创建。 普通组件放入 components ( App ),路由组件放入 views ( About、Home)。
在这里插入图片描述
About、Home中的内容:

import React from 'react'
export default function About() {
  return <div>About组件内容</div>
}
import React from 'react'
export default function Home() {
  return <div>Home组件内容</div>
}

Router 路由器组件

index.js

在 index.js 中,我们现在需要使用 路由器Router组件 来包裹。顶层的 Router 组件负责分析监听 URL 的变化,在它之下的 路由Route 组件(注意比 Router 少一个“r”)就可以直接读取这些信息。如果不这样做,路由效果就无法实现。

而 Router 又至少可分为两种:

第一种 BrowserRouter 。假设它用在本例中,我们这么做: / 对应 Home 页,/about 对应 About 页。但是这样的设计需要服务器端渲染,因为用户可能直接访问任何一个 URL,服务器端必须能对 / 的访问返回 HTML,也要对 /about的访问返回 HTML。
在这里插入图片描述
第二种 HashRouter 。它相比于第一种,需要通过 URL 后面的 # 部分来决定路由。比如:/#/ 对应 Home 页,/#/about 对应 About 页。在这种用法下,URL 中 # 之后的部分不会发送给服务器,所以无论哪个 URL,最后都是访问服务器的 / 路径,服务器也只需要返回同样一份 HTML 就可以,然后由浏览器端解析 # 后的部分,完成浏览器端渲染。
在这里插入图片描述
对于这两种,因为 create-react-app 产生的应用默认不支持服务器端渲染,所以平常写项目时,可以选择使用 HashRouter。但在实际产品中,最好还是用 BrowserRouter,这样用户体验更好。

import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
import App from './components/app'

ReactDOM.render(
  (
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  ),
  document.getElementById('root')
)

Link、Switch、Route、Redirect

app.jsx

在根组件 app.jsx 中会涉及到很多内容:

(使用以下内容时,一定需要:import {Route, Switch, Redirect, Link, NavLink} from 'react-router-dom'


(1)Link

对于单页应用我们已经知道,接下来将会在不同“页面”之间切换。之前无论是 a 标签做的,还是 div + click事件 做的,想要实现往往需要一个“导航栏”。现在我们不想让浏览器行为是网页跳转,所以不可能用之前的方式来做。

而现在的解决方案就是:用 react-router 提供的 Link 组件。它会在用户点击时,让 react-router 知晓这是一个单页应用的链接,不用网页跳转只做局部页面更新 (相当于 a 标签)。

演示代码如下:

<div className="list-group-item"><Link to='/about'>About</Link></div>
<div className="list-group-item"><Link to='/home'>Home</Link></div>

除此以外,还可以用 NavLink 组件 来实现该功能。NavLink 是 Link 的一个特定版本,会在匹配上当前的url的时候给已经渲染的元素添加参数。组件的属性有:

activeClassName(string):设置选中样式,默认值为active

activeStyle(object):当元素被选中时,为此元素添加样式

exact(bool):为true时,只有当导致和完全匹配class和style才会应用

strict(bool):为true时,在确定为位置是否与当前URL匹配时,将考虑位置pathname后的斜线

isActive(func):判断链接是否激活的额外逻辑的功能

演示代码如下:

<NavLink className="list-group-item" to='/about' activeClassName='activeClass'>About</NavLink>
<NavLink className="list-group-item" to='/home' activeClassName='activeClass'>Home</NavLink>

在这里使用的 activeClassName 是指该链接被选中时的样式。设置好样式后不要忘记导入css文件:import './xxx.css'

.activeClass {
  color: red !important;
}

Link 和 NavLink 的更多比对和用法可参考:React Router中Link和NavLink的学习总结,该有的内容文章都有写。


针对上面 NavLink 的用法,如果所有路由链接都有相同的 activeClassName,想要修改时就需要一个一个修改( 当然开发工具也有统一修改功能 )或者 有其它用法,为了更加方便管理,我们可以这样做:

<MyNavLink className="list-group-item" to='/about'>About</MyNavLink>
<MyNavLink className="list-group-item" to='/home'>Home</MyNavLink>

这里的 MyNavLink 是自定义的组件,不要忘记导入组件:import MyNavLink from './my-nav-link'

当我们对其样式或者功能要做出修改时,我们只需要去 MyNavLink 中,统一修改即可。需要注意的是:这里需要使用 props 才能知道要跳转的路由链接。( 那么props 中到底有什么内容将会在文章下方:向路由组件传递数据 说明)

import React from 'react'
import {NavLink} from 'react-router-dom'

export default function MyNavLink(props) {
  return <NavLink {...props} activeClassName='activeClass'/>
}

(2)Switch + Route

设置好路由链接之后,为了根据用户点击去显示各个路由组件的内容,需要使用 Switch 和 Route。演示代码:

<div className="panel-body">
	<Switch>
		<Route path='/about' component={About}/>
		<Route path='/home' component={Home}/>
	</Switch>
</div>

(3)Redirect

当我们设置好以上路由链接后,初始情况是不会进入任何路由链接的。如果我们想默认跳转到某路由链接,只需要使用 Redirect 即可。演示代码:

<div className="panel-body">
	<Switch>
		<Route path='/about' component={About}/>
		<Route path='/home' component={Home}/>
		<Redirect to='/about'/>
	</Switch>
</div>

完整代码:

import React from 'react'
import {Route, Switch, Redirect, Link, NavLink} from 'react-router-dom'
import About from '../views/about'
import Home from '../views/home'

export default class App extends React.Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header">
              <h2>React Router Demo</h2>
            </div>
          </div>
        </div>

        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/*导航路由链接*/}        
              <div className="list-group-item"><Link to='/about'>About</Link></div>
              <div className="list-group-item"><Link to='/home'>Home</Link></div>
              
              <NavLink className="list-group-item" to='/about'>About</NavLink>
              <NavLink className="list-group-item" to='/home'>Home</NavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/*可切换的路由组件*/}
                <Switch>
                  <Route path='/about' component={About}/>
                  <Route path='/home' component={Home}/>
                  <Redirect to='/about'/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

除此以外,还可以实现 动态路由。假设,我们增加一个新的页面叫 Product,对应路径为 /product,但是只有用户登录了之后才显示。如果用静态路由,我们在渲染之前就确定这条路由规则,这样即使用户没有登录,也可以访问 product,我们还不得不在 Product 组件中做用户是否登录的检查。

如果用动态路由,则只需要在代码中的一处涉及这个逻辑:

<div className="panel-body">
	<Switch>
		<Route path='/about' component={About}/>
		<Route path='/home' component={Home}/>
      	{
        	isUserLogin() &&
        	<Route path='/product' component={Product}/>,
      	}  
	</Switch>
</div>

嵌套路由

Route 路由组件 是可以嵌套的。现在就实现下图中的内容:在 Home 组件中,加入两个 子路由链接。
在这里插入图片描述
( 如果对相关基本语法感到困惑,可参照我的 ① ~ ④ 篇 测试语法文章)

my-nav-link 中的内容:( 之前提到的自定义NavLink组件 )

import React from 'react'
import {NavLink} from 'react-router-dom'

export default function MyNavLink(props) {
  return <NavLink {...props} activeClassName='activeClass'/>
}

home.jsx

在 home 路由组件 中,添加两个子路由链接,和之前的做法是相同的。只不过因为 子路由 在 home 下,所以 path 的路径如下设置即可。

import React from 'react'
import {Switch, Route, Redirect} from 'react-router-dom'
import MyNavLink from '../components/my-nav-link'
import News from './news'
import Message from './message'

export default function Home() {
  return (
    <div>
      <h2>Home组件内容</h2>
      <div>
        <ul className="nav nav-tabs">
          <li>
            <MyNavLink to='/home/news'>News</MyNavLink>
          </li>
          <li>
            <MyNavLink to="/home/message">Message</MyNavLink>
          </li>
        </ul>
        <Switch>
          <Route path='/home/news' component={News} />
          <Route path='/home/message' component={Message} />
          <Redirect to='/home/news'/>
        </Switch>
      </div>
    </div>
  )
}

News.jsx

import React from 'react'

export default class News extends React.Component {
  state = {
    newsArr: ['news001', 'news002', 'news003']
  }

  render () {
    return (
      <div>
        <ul>
          {
            this.state.newsArr.map((news, index) => <li key={index}>{news}</li>)
          }
        </ul>
      </div>
    )
  }
}

Message.jsx

import React from 'react'

export default class Message extends React.Component {
  state = {
    messages: [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
    ]
  }

  render () {
    return (
      <div>
        <ul>
          {
            this.state.messages.map((message, index) => <li key={index}>{message}</li>)
          }
        </ul>
      </div>
    )
  }
}

这些基本用法还是简单的,接下来在上面代码的基础上做其它功能:在 message 组件中,通过点击 路由链接,在下方内容区显示相关内容详情。( 按钮功能的实现 将在后续提及 )
在这里插入图片描述
在这里和以往遇到的问题不同的是:这次的几个路由链接中的内容是相似的,我们最好将这部分内容写在同一个组件中,否则在遇到更多相似内容的路由链接时,去为每个路由创建相关组件也不现实。所以该如何通过点击这些路由链接,让其内容详情子路由组件 message-detail 展现不同内容,就是这里需要解决的问题。


向路由组件传递数据

为了解决上述提到的问题,我们可以通过传递数据解决。简单地说:当我们点击某路由链接,那我们就将能够区别这些路由链接的标识传递出去,再在子组件中比对,得知要显示的是哪部分内容即可。React-Router 也允许我们向路由组件传递数据。

因此这部分数据可以这么设计:用 id 去作为标识。

  state = {
    messages: [
      {id: 1, title: 'Message001'},
      {id: 3, title: 'Message003'},
      {id: 6, title: 'Message006'},
    ]
  }

那么在子组件中,父组件到底传递了什么?

在这里会用到文章上方提到的一种用法。在之前 自定义 NavLink 组件时,我们就曾接收过父组件传递过来的数据,但是没有去查看。代码如下:

import React from 'react'
import {NavLink} from 'react-router-dom'

export default function MyNavLink(props) {
  return <NavLink {...props} activeClassName='activeClass'/>
}

那么现在去 message 组件的 render 中,我们输出 props,看一下从父组件中接收到了什么:

render () {
	console.log(this.props)
	...
}

输出结果:
在这里插入图片描述
在这里需要注意 history 和 match。


match

首先我们看一下里面的内容:
在这里插入图片描述
值得注意的是 path 和 params。这里的 path 就是当前路由链接的路径,params 就是从父组件中接收到的参数。正因为 params 的存在,我们就能够接收父组件传递过来的参数 。

对于 path 的作用:在嵌套路由的代码中我们看到:<Route path='/home/message' component={Message} />。那现在要添加的新路由是在 message 中,因此新 path 是要在之前 path 的基础上进行扩展。那如果真的路由层数很多的情况下,一直在 原path 的后面添加,肯定会觉得冗余,也有可能写错。因此,我们就可以用这个 path 来替代这部分内容。

在这里需要注意:之前我们使用 NavLink 或 Link,指向的路由链接都是用字符串表示的,这没有问题。但是现在,我们的 path 是获取到的:const path = this.props.match.path,所以肯定不能用之前字符串的方式表示,我们需要让其注意到这里的 path 是参数名。同样地,为了展现不同的内容,路由链接也需要根据 id 变化。而对于 JSX 的语法规则,如果想要让其以 JS 的语法解析,外层需要用 {。所以这样做:

<Link to={`${path}/${m.id}`}>{m.title}</Link>

它路由链接的结果是:/home/message/1/home/message/3/home/message/6 ,由后面的 id 来决定。因此在这里也就作出了区分。

把 Route 加上,message.jsx 的完整代码:

import React from 'react'
import {Link, Route} from 'react-router-dom'
import MessageDetail from "./message-detail"

export default class Message extends React.Component {
  state = {
    messages: []
  }

  componentDidMount () {
    // 模拟发送ajax请求
    setTimeout(() => {
      const data = [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
      ]
      this.setState({
        messages: data
      })
    }, 1000)
  }

  render () {
    console.log(this.props)
    const path = this.props.match.path

    return (
      <div>
        <ul>
          {
            this.state.messages.map((m, index) => {
              return (
                <li key={index}>
                  <Link to={`${path}/${m.id}`}>{m.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        <Route path={`${path}/:id`} component={MessageDetail}></Route>
      </div>
    )
  }
}

Route 里的 : 就是用来匹配URL的一个部分,直到遇到下一个 /、?、#为止。

现在去子组件中,我们看一下 props 是否接收到了参数:
在这里插入图片描述
这就证明了可以接收到参数。因此子组件 message-detail.jsx 的完整代码:

import React from 'react'

const messageDetails = [
  {id: 1, title: 'Message001', content: '我爱你, 中国'},
  {id: 3, title: 'Message003', content: '我爱你, 老婆'},
  {id: 6, title: 'Message006', content: '我爱你, 孩子'},
]

export default function MessageDetail(props) {

  console.log(props);
  const id = props.match.params.id
  const md = messageDetails.find(md => md.id===id*1)

  return (
    <ul>
      <li>ID: {md.id}</li>
      <li>TITLE: {md.title}</li>
      <li>CONTENT: {md.content}</li>
    </ul>
  )
}

history

接下来将会实现下图中按钮中的功能。
在这里插入图片描述
在这里,我们再去看在 message 组件输出的 props 中,想要实现这部分功能,将会用到这里的 history。
在这里插入图片描述
需要说的是, React Router 是建立在 history 之上的。 而这个 history 库是管理浏览器会话历史的工具库,它包装的是原生 BOM 中 window.history 和 window.location.hash 。简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。

里面的内容如下:
在这里插入图片描述
在这里就介绍常用的几个:

history.push():添加一个新的历史记录

history.replace():用一个新的历史记录替换当前的记录

history.goBack():回退到上一个历史记录

history.goForword():前进到下一个历史记录

history.go(n):前进 或 后退 第 n 个历史记录

history.push 这个方法会向 history 栈里面添加一条新记录,这个时候用户点击浏览器的回退按钮可以回到之前的路径。history.replace 跟 history.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样,替换掉当前的 history 记录。

对 history 感兴趣,可参考文章:react-router 中的history

使用起来比较简单,message.jsx 完整代码:

import React from 'react'
import {Link, Route} from 'react-router-dom'
import MessageDetail from "./message-detail"

export default class Message extends React.Component {
  state = {
    messages: []
  }

  componentDidMount () {
    // 模拟发送ajax请求
    setTimeout(() => {
      const data = [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
      ]
      this.setState({
        messages: data
      })
    }, 1000)
  }

  ShowDetail = (id) => {
    this.props.history.push(`/home/message/${id}`)
  }

  ShowDetail2 = (id) => {
    this.props.history.replace(`/home/message/${id}`)
  }

  back = () => {
    this.props.history.goBack()
  }

  forward = () => {
    this.props.history.goForward()
  }

  render () {
    console.log(this.props)
    const path = this.props.match.path

    return (
      <div>
        <ul>
          {
            this.state.messages.map((m, index) => {
              return (
                <li key={index}>
                  <Link to={`${path}/${m.id}`}>{m.title}</Link>
                  &nbsp;&nbsp;&nbsp;
                  <button onClick={() => this.ShowDetail(m.id)}>查看详情(push)</button>&nbsp;
                  <button onClick={() => this.ShowDetail2(m.id)}>查看详情(replace)</button>
                </li>
              )
            })
          }
        </ul>
        <p>
          <button onClick={this.back}>返回</button>&nbsp;
          <button onClick={this.forward}>前进</button>&nbsp;
        </p>
        <hr/>
        <Route path={`${path}/:id`} component={MessageDetail}></Route>
      </div>
    )
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值