ReactReact全家桶路由简介与React Router 5

Posted 前端More

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReactReact全家桶路由简介与React Router 5相关的知识,希望对你有一定的参考价值。

文章目录

1 路由的基本概念与原理

1.1 路由

路由是一个比较广义和抽象的概念,路由的本质就是一个映射关系(key: value),请求什么响应什么,key为路径,value可能为function或component,在开发中路由分为前端路由后端路由

1.2 前端路由

概念:根据不同的用户事件,显示不同的页面内容,value为component

注册路由: 当浏览器的path变为/test时, 当前路由组件就会变为Test组件

<Route path="/test" component=Test> 

本质:用户事件与事件处理函数之间的对应关系

1.3 后端路由

概念:根据不同的URL地址分发不同的资源,value为function

注册路由: 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

router.get(path, function(req, res))

本质:URL 请求地址与服务器资源之间的对应关系

1.4 SPA

由于后端渲染存在性能问题以及Ajax前端渲染不支持浏览器的前进后退操作,所以出现SPA技术

SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作,只有第一次会加载页面,以后的每次请求,仅仅是获取必要的数据,然后由页面中js解析获取的数据进行展示。

实现前端路由:基于URL中的hash实现,hash的变化会导致浏览器记录访问历史的变化、但是hash的变化不会触发新的URL请求,点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换。

2 react-router-dom 5

react-router-dom:

  • React的一个插件库
  • 专门用来实现一个SPA应用
  • 基于React的项目基本都会用到此库

2.1 基本组件

React Router中有三类内置组件:

  • router 组件<BrowserRouter><HashRouter>
  • 路由匹配组件<Route><Switch>
  • navigation 组件(<Link><NavLink><Redirect>

其它:

  • history对象
  • location对象
  • match对象
history:
	go: ƒ go(n)
	goBack: ƒ goBack()
	goForward: ƒ goForward()
	push: ƒ push(path, state)
	replace: ƒ replace(path, state)
location:
	pathname: "/about"
	search: ""
	state: undefined
match:
	params: 
	path: "/about"
	url: "/about"

安装包

  • 下载react-router-dom v5: npm install react-router-dom@5,若下载v6版本不需要加@5

  • 引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">

  • 安装完成后,上面所列出的这些组件,我们可以通过react-router-dom得到。

    import  BrowserRouter, Route, Link  from "react-router-dom";
    

2.2 router组件(BrowserRouter、HashRouter)

基于React Router的web应用,根组件应该是一个router组件(BrowserRouter、HashRouter)。 react-router-dom提供了两种路由,两种路由都会创建一个history对象。如果我们的应用有服务器响应web的请求,我们通常使用<BrowserRouter>组件;如果使用静态文件服务器,则我们应该使用 <HashRouter> 组件。

import  BrowserRouter  from "react-router-dom";
ReactDOM.render(
  // 路由组件将整个App包起来,保证使用的是同一个路由器
  <BrowserRouter>
    <App />
  </BrowserRouter>,
);

BrowserRouter和HashRouter区别:

底层原理不一样:

  • BrowserRouter调用的是H5 history API,低版本兼容性问题
  • HashRouter 使用的是URL哈希值

地址栏表现形式不一样:

  • BrowserRouter的路径:localhost:3000/demo/a

  • HashRouter的路径:localhost:3000/#/demo/a

刷新后对路由state参数的影响不一样:

  • BrowserRouter没有任何影响,因为state保存在history对象中。

  • HashRouter刷新后会导致路由state参数的丢失

官方会更推荐使用BrowserRouter,是因为其构建于H5的History API,比起hashRouter,它多出了更多的方法操控url。

建议:服务路由使用BrowerRoute(网页跳转),前端路由使用HashRouter(局部更新)

2.3 react-router路由模式

路由需要实现三个功能:

  • 当浏览器地址变化时,切换页面;
  • 点击浏览器后退,前进按钮,网页内容发生变化;
  • 刷新浏览器,页面加载内容对应当前路由对应的地址;

在单页面web网页中,单纯的浏览器地址改变,网页不会重载,如单纯的hash值改变,网页是不会变化的,因此我们的路由需要监听事件,并利用js实现动态改变网页。

react-router路由模式:

  • hash 模式:监听浏览器地址hash值变化,并执行相应的js切换。
  • history 模式:利用H5 history API实现url地址改变,网页内容改变。

2.3.1 hash模式

使用window.location.hash 属性和window.onhashchange事件,可以监听浏览器hash值的变化,去执行相应的js切换网页。

hash路由实现原理:

  1. hash 指的是地址中 # 号以及后面的字符。称为散列值
  2. 散列值不会随请求发送到服务器端的,所以改变hash,不会重新加载界面。
  3. 监听onhashchange事件,hash改变时,可以通过window.location.hash来获取和设置hash值。
  4. location.hash值的变化直接反应在浏览器的地址栏。

2.3.2 history模式

我们使用history首先应该了解window.history对象。它表示的是当前窗口的浏览历史,当发生改变时,只会改变路径,不会刷新界面。history对象就是一个堆栈

方法:

history.back():移动上一个网址,等同于浏览器的后退。

history.forward():移动到下一个网址,等同于浏览器前进。

history.go():接受一个参数,以当前网页为基准,来进行跳转。默认history.go(0),刷新当前界面。 history.go(-1) 相当于history.back( ), history.go(1) 相当于history.forward( );

history.pushState():往history堆栈中添加一条记录。不会刷新界面,只会导致history对象变化,地址栏发生变化。

history.replaceState():是替换当前history堆栈中最上层的记录。也是不会刷新界面,只会是histoty对象变化,地址栏发生变化。

每当history对象发生变化,就会触发popState事件: window.addEventListener(“popState”,function( ) )

  • 只调用pushState或者replaceState是不会触发该事件的,只有调用back,forward,go才会触发该事件。
  • 至此我们也能想到BrowserHisory是怎么利用history的API来实现url改变,网页内容发生改变。

2.4 路由匹配组件(Route、Switch)

react-router-dom中有两个匹配路由的组件: <Route><Switch>

import  Route, Switch  from "react-router-dom";

路由匹配是通过将 <Route>组件的path属性当前的location的pathname进行比较来完成的。 当一个<Route>匹配了 ,它所对应的组件内容将被渲染出来。 不匹配,则渲染null。如果一个 <Route> 没有path属性,他的组件对应内容将一直被渲染出来。

// 当 location =  pathname: '/about' 
<Route path='/about' component=About/> // 路径匹配成功,渲染 <About/>组件
<Route path='/contact' component=Contact/> // 路径不匹配,渲染 null
<Route component=Always/> // 该组件没有path属性,其对应的<Always/>组件会一直渲染

我们可以在组件树的任何位置放置 <Route> 组件。但是更常见的情况是将几个写在一起。 <Switch>组件可以用来将多个<Route>“包裹”在一起。

多个组件在一起使用时,并不强制要求使用<Switch>组件,但是使用<Switch>组件却是非常便利的,可以提高路由匹配效率(单一匹配)。<Switch>会迭代它下面的所有<Route>子组件,并只渲染第一个路径匹配的,这样只要匹配到了第一个就不会再往下匹配了。

<Switch>
  <Route exact path="/" component=Home />
  <Route path="/about" component=About />
  <Route path="/contact" component=Contact />
</Switch>

<Switch>
  // exact开启严格匹配,不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
  <Route exact path="/" component=Home />
  <Route path="/about" component=About />
  <Route path="/contact" component=Contact />
  /* 如果上面的Route的路径都没有匹配上,则 <NoMatch>被渲染,我们可以在此组件中返回404 */
  <Route component=NoMatch />
</Switch>

<Route> 匹配时所显示的组件,有三种写法

  • component
  • render
  • children

component

在使用时,如果我们想渲染的内容已经有对应的组件,则可以直接使用component的方法。例如:

<Route path="/user/:username" component=User />;

function User( match ) 
  return <h1>Hello match.params.username!</h1>;



render

render方法直接使用一个内联函数来渲染内容。

// convenient inline rendering
<Route path="/home" render=() => <div>Home</div>/>

2.5 Navigation组件(Link、NavLink、Redirect)

React Router提供了一个组件用来在应用中添加link。 当<Link>渲染时, 一个<a>标签在html页面中被创建出来。

<Link to="/">Home</Link>
// <a href='/'>Home</a>

<NavLink> 组件是一个特殊的<Link>组件。当它的path与当前location匹配时,可以自定义其样式来表示当前页面位置。NavLink可以实现路由链接的高亮,通过activeClassName属性指定样式名,默认是"active"

// location =  pathname: '/react' 
<NavLink to="/react" activeClassName="hurray">
  React
</NavLink>
// <a href='/react' className='hurray'>React</a>


当需要页面重定向时,我们可以使用 <Redirect> 组件。当一个组件被渲染时,页面将被渲染到 <Redirect> 组件的to属性指定的位置上。<Redirect> 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

<Route exact path="/" render=() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)/>

2.6 基本路由的使用

效果图

路由的使用原理:在导航区点击导航链接引起路径的变化,路径的变化被路由器检测到进行匹配组件,在展示区进行展示

路由的基本使用:

  1. 明确好界面中的导航区展示区
  2. 导航区的a标签改为Link标签, 当<Link>渲染时,一个<a>标签在html页面中被创建出来。
    <Link to="/xxxxx">Demo</Link>
  3. 展示区写Route标签进行路径的匹配
    <Route path='/xxxx' component=Demo/>
  4. 基于React Router的web应用,根组件应该是一个router组件(BrowserRouter,HashRouter),故<App> 的最外侧包裹了一个<BrowserRouter><HashRouter>

代码实现

index.js

//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//引入控制路由跳转BrowserRouter路由组件
import BrowserRouter from 'react-router-dom'
//引入App
import App from './App'

ReactDOM.render(
    //路由组件将整个App包起来,保证使用的是同一个路由器
    <BrowserRouter>
        <App/>
    </BrowserRouter>,
    document.getElementById('root')
)

App.jsx

import React,  Component  from 'react'
import  Link,Route  from 'react-router-dom'
import Home from './Home'
import About from './About'

export default class App extends 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">
            /* 在React中靠路由链接实现切换组件--编写路由链接, */
            <Link className="list-group-item" to="/about">About</Link>
            <Link className="list-group-item" to="/home">Home</Link>
          </div>
        </div>
        <div className="col-xs-6">
          <div className="panel">
              <div className="panel-body">
                /* 注册路由 */
                <Route path='/about' component=About />
                <Route path='/home' component=Home />
            </div>
          </div>
        </div>
      </div>
    </div>
    )
  


2.7 路由组件与一般组件的区别

  • 写法不同
    一般组件:<Demo/>
    路由组件:<Route path="/demo" component=Demo/>

  • 存放位置不同
    一般组件:components
    路由组件:pages

  • 接收到的props不同(最大的区别)
    一般组件:写组件标签时传递了什么,就能收到什么
    路由组件:接收到三个固定的属性history、location、match

//history
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)

//location
pathname: "/about"
search: ""
state: undefined

//match
params: 
path: "/about"
url: "/about"

解决多级路径刷新页面样式丢失的问题:

  • public/index.html 中 引入样式时不写 .// (常用)【绝对路径】
  • public/index.html 中 引入样式时不写 ./%PUBLIC_URL% (常用)
  • 使用HashRouter

push模式与replace模式:

 //push模式(默认):可以留下操作记录,不发生替换
 //replace模式:开启的路由不会留下操作记录,发生替换
 <Link replace to="/about">About</Link>

2.8 嵌套路由使用

效果图

注意:

  1. 注册子路由时要写上父路由的path值
  2. 路由的匹配是按照注册路由的顺序进行的

代码实现

Home/index.jsx

import React,  Component  from 'react'
import  Route, NavLink,Redirect,Switch  from 'react-router-dom'
import News from './News'
import Message from './Message'

export default class Home extends Component 
  render() 
    return (
      <div>
        <h3>我是Home的内容</h3>
        <div>
          <ul className="nav nav-tabs">
            <li>
              <NavLink className="list-group-item" to="/home/news">News</NavLink>
            </li>
            <li>
              <NavLink className="list-group-item" to="/home/message">Message</NavLink>
            </li>
          </ul>
          <Switch>
            <Route path='/home/news' component=News />
            <Route path='/home/message' component=Message />
            <Redirect to='/home/news' />
          </Switch>
        </div>
      </div>
    )
  


2.9 向路由组件传递参数的方法

效果图

2.9.1 方法一:传递params参数

向路由组件传递params参数:

  • 路由链接(携带参数):<Link to='/demo/test/tom/18'>详情</Link>
  • 注册路由(声明接收):<Route path="/demo/test/:name/:age" component=Test/>
  • 接收参数:this.props.match.params

:路由中的params参数类似于ajax中的params参数

代码实现

Message/index.jsx

import React,  Component  from 'react'
import  Link, Route  from 'react-router-dom';
import Detail from './Detail';

export default class Message extends Component 
  state = 
    messageArr: [
       id: '01', title: '消息1' ,
       id: '02', title: '消息2' ,
       id: '03', title: '消息3' ,
    ]
  
  render() 
    const  messageArr  = this.state;
    return (
      <div>
        <ul>
          
            messageArr.map((msgObj) => 
              return (
                <li key=msgObj.id>
                  /* 向路由组件传递params参数 */
                  <Link to=`/home/message/detail/$msgObj.id/$msgObj.title`>                             msgObj.title</Link>
                </li>
              )
            )
          
        </ul>
        <hr />
        /* 声明接收params参数 */
        <Route path="/home/message/detail/:id/:title" component=Detail/>
      </div>
    )
  


Detail/index.jsx

import React,  Component  from 'react'

export default class Detail extends Component 
  state = 
    detailData : [
       id: '01', content: '你好啊' ,
       id: '02', content: '还不错鸭' ,
       id: '03', content: '显示我吧' 
    ]
  
  render() 
    console.log(this.props)
    // 接收params参数
    const  id, title  = this.props.match.params
    const findResult= this.state.detailData.find((dataObj) => 
      return dataObj.id === id
    )
    return (
      <div>
        <ul>
          <li>ID: id </li>
          <li>Title: title </li>
          <li>Content:  findResult.content</li>
        </ul>
      </div>
    )
  


2.9.2 方法二:传递search参数

向路由组件传递search参数:

  • 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'>详情</Link>

  • 注册路由(无需声明,正常注册即可): <Route path="/demo/test" component=Test/>

  • 接收参数:this.props.location.search

:获取到的searchurlencoded编码字符串,形式为key=value&key=value,需要借助querystring解析,路由中search参数类似于ajax中的query参数

import qs from 'querystring'
let obj = name:'tom', age:18
console.log(qs.stringify(obj)) // name=tom&age=18
let str = 'carName=Benz&price=199'
console.log(qs.parse(str)) // carName: 'Benz', price: 199

代码实现

Message/index.jsx

import React,  Component  from 'react'
import  Link, Route  from 'react-router-dom';
import Detail from '

以上是关于ReactReact全家桶路由简介与React Router 5的主要内容,如果未能解决你的问题,请参考以下文章

ReactReact全家桶React 概述+虚拟DOM的创建与渲染+JSX

ReactReact全家桶React 概述+虚拟DOM的创建与渲染+JSX

ReactReact全家桶React Hooks

ReactReact全家桶React Hooks

ReactReact全家桶React Router 6

ReactReact全家桶之React Router6