React路由--学习笔记

481 阅读10分钟

1. 相关理解

1.1. SPA的理解

  1. 单页Web应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

1.2. 路由的理解

  1. 什么是路由?

    1. 一个路由就是一个映射关系(key:value)
    2. key为路径, value可能是function或component
  2. 路由分类

    1. 后端路由:

      1. 理解: value是function, 用来处理客户端提交的请求。
      2. 注册路由: router.get(path, function(req, res))
      3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
    2. 前端路由:

      1. 浏览器端路由,value是component,用于展示页面内容。
      2. 注册路由:
      3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

1.3. react-router-dom的理解

  1. react的一个插件库。
  2. 专门用来实现一个SPA应用。
  3. 基于react的项目基本都会用到此库。

1.4 react 如何实现SPA?

单页面 多组件 借助history

BOM上就有History,但是一般不直接使用BOM上的History(操作繁琐),而是引用一个history的库来操作history,不过结果也是靠这个history去操作BOM上的history来实现,主要是为了操作简单

  • 流程

    1. 点击导航区
    2. 游览器的路径被修改(游览器可以监测路径是否被修改history.listen(location=>{})
    3. 展示对应组件
  • 方法1:直接调用H5推出的history身上的API

    let history=History.createBrowserHistory()

    xxxxxx/home

  • 方法2:hash值(锚点#)兼容性较好

    let history=History.createHashHistory()

    xxxxxx/#/home —锚点也是不会导致页面刷新的

2. react-router-dom相关API

2.1. 内置组件

  1. <BrowserRouter>
  2. <HashRouter>
  3. <Route>
  4. <Redirect>
  5. <Link>
  6. <NavLink>
  7. <Switch>

2.2. 其它

  1. history对象
  2. match对象
  3. withRouter函数

3. 基本路由使用

3.1. 效果

react-router demo1 (3).gif

3.2. 准备

  1. 下载react-router-dom: npm install --save react-router-dom
  2. 引入bootstrap.css:

3.2. 代码

index.js

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

ReactDOM.render(
  //用<BrowserRouter>包裹
    <BrowserRouter>
      <App />
    </BrowserRouter>,
  document.getElementById('root')
);
// ReactDOM.render(<App/>,document.getElementById('root'))

App.js

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


// 创建并暴露组件
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">
                            {/* 原生html中,靠<a>跳转不同的页面 */}
                            {/* <a className="list-group-item" href="./about.html">About</a>
                            <a className="list-group-item active" href="./home.html">Home</a> */}
              
                            {/* 在React中靠路由链接实现切换组件----编写路由链接 */}
                            <Link className="list-group-item active" 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">
                            {/*注册路由 ----v6需用<Routes>包裹*/}
                            <Routes>
                                <Route path="/about" element={<About/>}/>
                                <Route path="/home" element={<Home/>}/>
                            </Routes>
                            
                        </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
// 暴露App组件
// export default App

components—About

import React, { Component } from 'react'
export default class About extends Component {
    render() {
        return (
            <h3>
                我是About 
            </h3>
        )
    }
}

components—Home

import React, { Component } from 'react'
export default class Home extends Component {
    render() {
        return (
            <h3>我是Home</h3>
        )
    }
}

3.3.注意点

1.明确好界面中的导航区、展示区

2.导航区的a标签改为Link标签

  • 原生html中,靠<a>跳转不同的页面
<a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> 
  • react
<Link to="/xxxxx">Demo</Link>

3.展示区写Route标签进行路径的匹配

<Route path='/about' component={About}/>
<Route path='/home' component={Home}/>
//v6:
<Routes>
    <Route path="/about" element={<About/>}/>
    <Route path="/home" element={<Home/>}/>
</Routes>

4.的最外侧包裹了一个或

  • 包裹和的区别??
    • 包裹采用多级路由时,

      1. public/index.html 中 引入样式时不写 ./ 写 / (常用)
        • 加.会导致多层级路由时寻找bootstrap样式时路径错误
      2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
    • 包裹采用多级路由时

      可以写./

4. 基本路由的扩展

4.1.NavLink的使用

App.js

import React,{Component} from 'react' 
import {Routes,Route,Navigate} from 'react-router-dom' 
import MyNavLink from './components/MyNavLink'  //封装MavLink组件
import About from './pages/About'
import Home from './pages/Home'
import Header from './components/Header'

// 创建并暴露组件
export default class App extends Component{
    render(){
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <Header/>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
                        {/**NavLink 表示点了谁就给谁加一个active */}
                        {/* <NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} to="/about">About</NavLink> *v6版本activeClassName不生效 */}
                        {/* <NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} to="/home">Home</NavLink> */}
                        {/* 封装NavLink 注意:标签体内容属于特殊的标签属性,会在子组件的props.children中得到*/}
                        <MyNavLink to="/about">About</MyNavLink>
                        <MyNavLink to="/home/a/b">Home</MyNavLink> {/**模糊匹配:输入的路径必须包含要匹配的路径且顺序一致,添加exact可开启严格匹配 */}
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                        <div className="panel-body">
                            {/*注册路由 */}
                            <Routes>
                                <Route path="/about" element={<About/>}/>
                                <Route path="/home" element={<Home/>}/>
                                {/* <Route exact path="/home" element={<Home/>}/> exact使用严格匹配*/}
                                {/* 解决路由重定向,使得在匹配不成功时用来兜底的 */}
                                {/* <Redirect to="/about"/> v6版本移除Redirect了*/}
                                <Route path="*" element={<Navigate to="/about" />} />
                            </Routes>
                            
                        </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
// 暴露App组件
// export default App

pages—About

import React, { Component } from 'react'
export default class About extends Component {
    render() {
        return (
            <h3>
                我是About 
            </h3>
        )
    }
}

pages—Home

import React, { Component } from 'react'
export default class Home extends Component {
    render() {
        return (
            <h3>我是Home</h3>
        )
    }
}

components—Header

import React, { Component } from 'react'

export default class Header extends Component {
  render() {
    // console.log('Header组件收到的props是',this.props);
    return (
      <div className="page-header"><h2>React Router Demo</h2></div>
    )
  }
}

components—MyNavLink

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


export default class MyNavLink extends Component {
    render() {
        return (
            <div>
                {/* ...实现批量传递,包括标签体About和Home */}
                <NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} {...this.props}/>
            </div>
        )
    }
}

4.2 注意点

路由组件与一般组件

1.写法不同:

  • 一般组件:

  • 路由组件:<Route path="/about" element={/>

2.存放位置不同:

  • 一般组件:components

  • 路由组件:pages

3.接收到的props不同:

  • 一般组件:写组件标签时传递了什么,就能收到什么

  • 路由组件:接收到三个固定的属性

    • 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"

NavLink与封装NavLink

  1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名(v6版本activeClassName不生效)
  2. v6可采用三元式
className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")}

Switch的使用(已弃用)

  1. 通常情况下,path和component是一一对应的关系。
  2. Switch可以提高路由匹配效率(单一匹配)。
<Switch>
  <Route path="/about" component={About}/>
  <Route path="/home" component={Home}/>
  <Route path="/home" component={Test}/> {/*不匹配*/}
</Switch>

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

  1. public/index.html 中 引入样式时不写 ./ 写 / (常用)
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
  3. 使用HashRouter
<link rel="stylesheet" href="./css/bootstrap.css"> <!--加.会导致多层级路由时寻找bootstrap路径错误-->
<link rel="stylesheet" href="/css/bootstrap.css">
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">  <!--这种方法也行 或者把broswerrouter改为hashrouter -->

路由的严格匹配与模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

  2. 开启严格匹配:

    <Route exact={true} path="/about" component={About}/>
    
    //v6: 
    <Route exact path="/home" element={<Home/>}/>
    
  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用(已弃用)

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

  2. 具体编码:

    <Switch>
      <Route path="/about" component={About}/>
      <Route path="/home" component={Home}/>
      <Redirect to="/about"/>
    </Switch>
    
  3. v6写法:

    <Routes>
      <Route path="/about" element={<About/>}/>
      <Route path="/home" element={<Home/>}/>
      <Route path="*" element={<Navigate to="/about" />} />
    </Routes>
    

5. 嵌套路由使用

5.1.效果

react-router demo2.gif

5.2.代码

App.js

import React,{Component} from 'react' 
import {Routes,Route,Navigate} from 'react-router-dom' 
import MyNavLink from './components/MyNavLink'
import About from './pages/About'
import Home from './pages/Home'
import Header from './components/Header'

// 创建并暴露组件
export default class App extends Component{
    render(){
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <Header/>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
                        <MyNavLink to="/about">About</MyNavLink>
                        <MyNavLink to="/home">Home</MyNavLink> 
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                        <div className="panel-body">
                            {/*注册路由 */}
                            <Routes>
                                <Route path="/about" element={<About/>}/>
                                <Route path="/home/*" element={<Home/>}/> {/**v6需加/*才能实现二级路由  */}
                                <Route path="*" element={<Navigate to="/about" />} />
                            </Routes>
                        </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
// 暴露App组件
// export default App

pages—Home

import React, { Component } from 'react'
import {Routes,Route,Navigate} from 'react-router-dom' 
import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Messages from './Messages'

export default class Home extends Component {
    render() {
        return (
            <div>
                <h3>我是Home</h3>
                <div>
                    <ul className="nav nav-tabs">
                        <li>
                            <MyNavLink to="news">News</MyNavLink>
                            {/* <MyNavLink to="/home/news">News</MyNavLink>  v6中不需要如此 */}
                        </li>
                        <li>
                            <MyNavLink to="message">Message</MyNavLink>
                        </li>
                    </ul>
                    <Routes>
                        <Route path="news"  element={<News/>}/>
                        {/* <Route path="/home/news" component={News}/>  v6中不需要如此,且上级路由path后缀需加/*  */}
                        <Route path="message"  element={<Messages/>}/>
                        <Route path="*" element={<Navigate to="news" />} />
                    </Routes>
                </div>
            </div>
        )
    }
}

pages—Home—News

import React, { Component } from 'react'

export default class News extends Component {
    render() {
        return (
            <div>
                <ul>
                    <li>news001</li>
                    <li>news002</li>
                    <li>news003</li>
                </ul>
            </div>
            
        )
    }
}

pages—Home—Messages

import React, { Component } from 'react'

export default class Messages extends Component {
    render() {
        return (
            <div>
                <ul>
                    <li>
                        <a href="/message1">message001</a>&nbsp;&nbsp;
                    </li>
                    <li>
                        <a href="/message2">message002</a>&nbsp;&nbsp;
                    </li>
                    <li>
                        <a href="/message/3">message003</a>&nbsp;&nbsp;
                    </li>
                </ul>
            </div>
           
        )
    }
}

5.3.注意点

v5:

1.注册子路由时要写上父路由的path值

2.路由的匹配是按照注册路由的顺序进行的

v6:

1.父路由需加/*才能实现二级路由

2.子路由不需要写父路由的path值

3.路由的匹配是按照注册路由的顺序进行的

6. 向路由组件传递参数数据

6.1.效果

react-router demo3 (2).gif

6.2.向路由组件传递params参数

pages—Home—Messages

import React, { Component } from 'react'
import Detail from './Detail'
import {Link,Routes,Route} from 'react-router-dom' 

export default class Messages 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 (
                                <div>
                                    <li key={msgObj.id}>
                                        {/* 向路由组件传递params参数
                                        <Link to={`detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>&nbsp;&nbsp; */}

                                        {/* 向路由组件传递search参数 */}
                                        {/* <Link to={`detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>&nbsp;&nbsp; */}

                                         {/* 向路由组件传递state参数 */}
                                         {/* <Link to={{pathname:'detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>&nbsp;&nbsp; */}
                                         <Link to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>
                                    </li>
                                </div>
                                
                            )
                        })
                    }
                </ul>
                <hr/>
                <Routes>
                    {/* 声明接收params参数
                    <Route path="detail/:id/:title" element={<Detail/>}/> */}

                    {/*search参数无需声明接收 */}
                    {/* <Route path="detail" element={<Detail/>}/> */}

                    {/*state参数无需声明接收 */}
                    <Route path="detail" element={<Detail/>}/>
                </Routes>
            </div>
           
        )
    }
}

pages—Home—Messages—Detail(params使用函数式组件写法)

// import React, { Component } from 'react'
import {useParams} from 'react-router-dom' 

const DetailData=[
    {id:'01',content:'消息1的内容'},
    {id:'02',content:'消息2的内容'},
    {id:'03',content:'消息3的内容'}
]

// v6版本需要使用function
export default function Detail(){
    let params=useParams()
    const findcontent=DetailData.find((detailObj)=>{
        return detailObj.id===params.id
    })||{}
    return (
        <ul>
            <li>ID:{params.id}</li>
            <li>TITLE:{params.title}</li>
            <li>CONTENT:{findcontent.content}</li>
        </ul>
    )
}

pages—Home—Messages—Detail(search和state使用函数式组件写法)

import React, { Component } from 'react';
import { useSearchParams } from 'react-router-dom';

// v6使用class组件。需要封装一下。利用hoc组件来获取参数,然后传递给class组件

//search写法
/*
function myWithRouter(Detail) {
  return (props) => {
    // let [searchParams, setSearchParams] = useSearchParams();
    let [searchParams] = useSearchParams();
    const params = {
      id: searchParams.get('id'),
      title: searchParams.get('title')
    }
    return <Detail {...props} params={params} />
  }
}
*/

//state写法
function myWithRouter(Detail) {
  return (props) => {
    let location = useLocation();
    /*
      这里写成 location.state || {}
      因为state在刷新后可能为空,所以这里为空时设置为空对象
    */
    const stateObj = location.state || {};
    const params = {
      id: stateObj.id,
      title: stateObj.title
    }
    return <Detail {...props} params={params} />
  }
}

//以下通用
const DetailData = [
  { id: '01', content: '11111111111111111111' },
  { id: '02', content: '22222222222222222222' },
  { id: '03', content: '33333333333333333333' }
]

class Detail extends Component {
  render() {
    const contentStr = DetailData.find((detailObj) => {
      return detailObj.id === this.props.params.id;
    })
   return (
    <ul>
      <li>ID:{this.props.params.id}</li>
      <li>TITLE:{this.props.params.title}</li>
      <li>CONTENT:{contentStr.content}</li>
     </ul>
     )
  }
}

export default myWithRouter(Detail); 

6.3.注意点

params参数

  • 路由链接(携带参数):
<Link to={/home/message/detail/${msgObj.id}/${msgObj.title}}>{msgObj.title}</Link>
  • 注册路由(声明接收):
<Route path="/home/message/detail/:id/:title" component={Detail}/>
  • 接收参数:
const {id,title} = this.props.match.params
  • v6:
    • 路由不需要写父路由路径

    • useParams接收参数

      let params=useParams()
      
    • 不使用类式组件,需使用函数式组件

    • 或者采用高阶组件方式,定义一个函数组件包裹类式组件

search参数

  • 路由链接(携带参数):
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
  • 注册路由(无需声明,正常注册即可):
<Route path="/home/message/detail" component={Detail}/>
  • 接收参数:this.props.location.search

    备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
  • v6:

    • useSearchParams
    let [searchParams] = useSearchParams();
    const params = {
      id: searchParams.get('id'),
      title: searchParams.get('title')
    }
    

state参数

  • 路由链接(携带参数):
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
  • 注册路由(无需声明,正常注册即可):
<Route path="/home/message/detail" component={Detail}/>
  • 接收参数:
const {id,title} = this.props.location.state || {}
  • v6:

    • 路由链接(携带参数):
    <Link to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>
    
    • useLocation

      备注:刷新也可以保留住参数

    let location = useLocation();
    /*
      这里写成 location.state || {}
      因为state在刷新后可能为空,所以这里为空时设置为空对象
    */
    const stateObj = location.state || {};
    const params = {
      id: stateObj.id,
      title: stateObj.title
    }
    

7. 多种路由跳转方式

7.1效果

react-router demo4.gif

7.2 代码

pages—Home—Messages

import React from 'react'
import Detail from './Detail'
import {Link,Routes,Route,useNavigate} from 'react-router-dom' 

//v6版本使用编程式路由时
//1.需把类式组件改为函数式组件,或者在类组件外面包裹一层函数组件
//2.this.props.history.push无法使用,需使用useNavigate
export default function Messages() {
    let Navigate = useNavigate();
    let state={
        messageArr:[
            {id:'01',title:'消息1'},
            {id:'02',title:'消息2'},
            {id:'03',title:'消息3'}
        ]
    }
    function showPush(id,title){
        // this.props.history.push(`/home/message/detail/${id}/${title}`)
        Navigate(`/home/message/detail/${id}/${title}`,{replace:false})
    }
    function showReplace(id,title){
        // this.props.history.replace(`/home/message/detail/${id}/${title}`)
        Navigate(`/home/message/detail/${id}/${title}`,{replace:true})  
    }
    function back(){
        Navigate(-1)
    }
    function forward(){
        Navigate(1)
    }
    function go(){
        Navigate(2)
    }
    // render() {
        const{messageArr}=state
        return (
            <div>
                <ul>
                    {
                        messageArr.map((msgObj)=>{
                            return (
                                <div key={msgObj.id}>
                                    <li>
                                        {/* 向路由组件传递params参数 */}
                                        <Link to={`detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>&nbsp;&nbsp;
                                        &nbsp;<button onClick={()=>showPush(msgObj.id,msgObj.title)}>push跳转</button>
                                        &nbsp;<button onClick={()=>showReplace(msgObj.id,msgObj.title)}>replace跳转</button>
                                    </li>
                                </div>
                                
                            )
                        })
                    }
                </ul>
                <hr/>
                {/* 声明接收params参数 */}
                <Routes>
                    <Route path="detail/:id/:title" element={<Detail/>}/>   
                </Routes>
                <button onClick={()=>back()}>回退</button>
                <button onClick={()=>forward()}>前进</button>
                <button onClick={()=>go()}>走2</button>
            </div>
           
        )
    // }
}

7.3.注意点

push会留下历史痕迹,replace直接替换

  • 借助this.props.history对象上的API对操作路由跳转、前进、后退

    -this.prosp.history.push()

    -this.prosp.history.replace()

    -this.prosp.history.goBack()

    -this.prosp.history.goForward()

    -this.prosp.history.go()

  • v6:

    1.需把类式组件改为函数式组件,或者在类组件外面包裹一层函数组件

    2.this.props.history.push无法使用,需使用useNavigate

8.BrowserRouter与HashRouter的区别

1.底层原理不一样:

BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

HashRouter使用的是URL的哈希值。

2.path表现形式不一样

BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

HashRouter的路径包含#,例如:localhost:3000/#/demo/test

3.刷新后对路由state参数的影响

(1).BrowserRouter没有任何影响,因为state保存在history对象中。

(2).HashRouter刷新后会导致路由state参数的丢失!!!

4.备注:HashRouter可以用于解决一些路径错误相关的问题。