React-路由 react-router-dom

Posted 廖若星辰LTY

tags:

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

前端路由的功能:让用户从一个页面跳转到另一个页面。
前端路由是一套映射规则,在 React 中,是 URL 路径与组件的对应关系
使用 React 路由简单来说就是配置路径与组件(配对)

路由的本质: 一个路径 path 对应唯一的一个组件 component 当我们访问一个 path 自动把 path 对应的组件进行渲染。

React 路由基本使用

1.安装

首先你需要有一个 React 项目:npx create-react-app my-app (这是 React 脚手架初始化项目)

注:React 路由使用前需要先安装 react-router-dom 的包,React 脚手架初始化项目时不会自动安装这个包,需要我们手动再安装

安装包yarn add react-router-domnpm i react-router-dom@6

(注:本文使用的 react-router-dom 版本为 ^6.3.0

2.按需导入包中的核心组件

import  BrowserRouter, Routes, Route, Link  from 'react-router-dom'

说明:

  • BrowerRouter:包裹整个应用,一个 React 应用只需要使用一次,这个组件可以和 HashRouter 替换,二者主要区别如下:

模式

实现方式

路由url表现

HashRouter

监听url hash值实现

http://localhost:3000/#/about

BrowerRouter

h5的 history.pushState API实现

http://localhost:3000/about

  •  Link:用于指定导航链接,完成声明式的路由跳转 类似于 <router-link />(渲染到 DOM 中它其实就是一个 a 链接
  •  Routes:提供一个路由出口,组件内部会存在多个内置的 Route 组件,满足条件的路由会被渲染到组件内部(类比 router-view)
  • Route:用于定义路由路径 path 和渲染组件 element 的对应关系 [element:因为 react 体系内把组件叫做 react element]

代码示例

import  BrowserRouter, Routes, Route, Link  from 'react-router-dom'

function Home () 
  return (
    <p>这是首页的内容</p>
  )

function About () 
  return (
    <p>这是关于的内容</p>
  )

function App () 
  return (
    // 声明当前要用一个非hash模式的路由
    <BrowserRouter>
      <div className="App">
        /* 指定跳转的组件,to 用来配置路由地址 */
        <Link to="/">首页</Link><br />
        <Link to="/about">关于</Link>
        /* 路由出口:路由对应的组件会在这里进行渲染 */
        <Routes>
          /* 指定路由路径和组件的对应关系:path 代表路径,element 代表对应的组件,它们成对出现 */
          <Route path='/' element=<Home />></Route>
          <Route path='/about' element=<About />></Route>
        </Routes>
      </div>
    </BrowserRouter>
  )


export default App

编程式导航

注:上面使用 <Link to="/">跳转</Link> 的方式是:声明式导航

编程式导航: 通过 js 编程的方式进行路由页面跳转,比如说从首页跳转到关于页

实现步骤

  1. 导入一个 useNavigate 钩子函数
  2. 执行 useNavigate 函数 得到 跳转函数
  3. 在事件中执行跳转函数完成路由跳转

示例代码

// 导入useNavigate函数
import  useNavigate  from 'react-router-dom'
const Home = () => 
  // 执行函数
  const navigate = useNavigate()
  return (
    <div>
      Home
      <button onClick= ()=> navigate('/about') > 跳转关于页 </button>
    </div>
  )


export default Home

注: 如果在跳转时不想添加历史记录,可以添加额外参数 replace 为 true

navigate('/about',  replace: true  )

路由传参

跳转路由的同时,很多时候都需要传递参数,这里有两种传参方式:

1. searchParams传参

路由传参

    navigate('/?id=1001&name=zs',  replace: true )

路由取参

import  useSearchParams  from 'react-router-dom'
// 在Home中读取页面传递过来的参数(以路径拼接的方式传递过来的参数)
function Home () 
  const [params] = useSearchParams()
  // params 是一个对象,对象里有一个get方法用来获取对应的参数
  // 把参数的名称作为get方法的实参传递即可
  const id = params.get('id')
  const name = params.get('name')
  return (
    <p>这是首页的内容,首页获取到的参数id:id,name:name</p>
  )

2.params传参

路由传参

    navigate('/about/1002/lisi',  replace: true )

设置参数占位符

/* 使用 params 传参和接收参数时,指定路由路径时需要提前使用“占位符”给参数进行占位 */
<Route path='/about/:id/:name' element=<About />></Route>

路由取参

import  useParams  from 'react-router-dom'
// 在关于页面接收登录页使用 params 传参的方式传递过来的参数
function About () 
  const params = useParams()
  return (
    <p>这是关于的内容,接收到登录页传递过来的id为:params.id,name为params.name</p>
  )

注:以上两种路由传参的方式各有优劣,没有说一定要使用哪一种,个人根据习惯使用即可。

嵌套路由

实现步骤

  1. 在一级路由的 <Route></Route>定义嵌套路由声明
  2. 在该一级路由组件内部通过 <Outlet /> 指定二级路由出口

示例代码

import  BrowserRouter, Routes, Route, Link, Outlet  from 'react-router-dom'
const Home = () => 
  return (
    <div>
      <p>首页</p>
      <Link to='/'>to board</Link><br />
      <Link to='/artacle'>to artacle</Link><br />
      /* 定义二级路由出口,嵌套的路由对应组件将渲染到此处 */
      <Outlet />
    </div>
  )

const Board = () => 
  return (
    <div>这是 Board 组件,这是默认显示的二级组件</div>
  )

const Artacle = () => 
  return (
    <div>这是 Artacle 组件</div>
  )

function App () 
  return (
    <BrowserRouter>
      <div className="App">
        <Routes>
          /* 一级路由 */
          <Route path='/' element=<Home />>
            /* 在一级路由内部嵌套二级路由 */
            /* 默认二级:添加 index 属性,把它自己的 path 去掉即可 */
            <Route index element=<Board />></Route>
            /* <Route path='board' element=<Board />></Route> */
            <Route path='artacle' element=<Artacle />></Route>
          </Route>
        </Routes>
      </div>
    </BrowserRouter>
  )


export default App

注:默认二级路由:(见示例代码)

实现步骤:

  1. 默认二级路由标记 index 属性
  2. 把原本的路径 path 属性去掉

404路由配置

场景:当 url 的路径在整个路由配置中都找不到对应的 path ,使用404兜底组件进行渲染。

实现步骤:

  1. 准备一个 NotFound 组件(404的页面)
  2. 把该组件的路由对应关系配置为 Routes 内部的一级路由,path='*'

示例代码

import  BrowserRouter, Routes, Route  from 'react-router-dom'

// 定义 404 组件
const NotFound = () => 
  return (
    <h1>404  抱歉,页面未找到TAT</h1>
  )

function App () 
  return (
    <BrowserRouter>
      <div className="App">
        <Routes>
          /* 一级路由 */
          /* 当所有路径都没有匹配到时渲染此组件 作为兜底 */
          <Route path='*' element=<NotFound />></Route>
        </Routes>
      </div>
    </BrowserRouter>
  )


export default App

集中式路由配置

场景: 当我们需要路由权限控制点时候, 对路由数组做一些权限的筛选过滤,所谓的集中式路由配置就是用一个数组统一把所有的路由对应关系写好替换本来的 Roues 组件

示例代码

import  BrowserRouter, Routes, Route, useRoutes  from 'react-router-dom'

import Layout from './pages/Layout'
import Board from './pages/Board'
import Article from './pages/Article'
import NotFound from './pages/NotFound'

// 1. 准备一个路由数组 数组中定义所有的路由对应关系
const routesList = [
  
    path: '/',
    element: <Layout />,
    children: [
      
        element: <Board />,
        index: true, // index设置为true 变成默认的二级路由
      ,
      
        path: 'article',
        element: <Article />,
      ,
    ],
  ,
  // 增加n个路由对应关系
  
    path: '*',
    element: <NotFound />,
  ,
]

// 2. 使用useRoutes方法传入routesList生成Routes组件
function WrapperRoutes() 
  let element = useRoutes(routesList)
  return element


function App() 
  return (
    <div className="App">
      <BrowserRouter>
        /* 3. 替换之前的Routes组件 */
        <WrapperRoutes />
      </BrowserRouter>
    </div>
  )


export default App

ReactSPA - 路由机制 - react-router-dom - 基本路由 - 嵌套路由 - 传递参数 - 路由跳转

1. 相关理解

1.1. SPA的理解

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

1.2. 路由的理解

1.2.1 什么是路由?

  1. 一个路由就是一个映射关系(key: value)
  2. key为路径, value可能是functioncomponent

1.2.2 路由分类

1. 后端路由

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

2. 前端路由

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

1.3. react-router-dom 的理解

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

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. 效果

在这里插入图片描述

3.2. 准备

  1. 下载react-router-dom: npm install react-router-dom
    在这里插入图片描述
  2. 引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">

3.3 路由的基本使用

  1. 明确好界面中的导航区、展示区
  2. 导航区的a标签改为Link标签
    <Link to="/xxxxx">Demo</Link>
  3. 展示区写Route标签进行路径的匹配
    <Route path='/xxxx' component={Demo}/>
  4. <App>的最外侧包裹了一个<BrowserRouter><HashRouter>

3.4 实现

index.js

这里用一个标签将整个App包起来,保证使用的是同一个路由器

//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//
import {BrowserRouter} from 'react-router-dom'
//引入App
import App from './App'

ReactDOM.render(
	<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">
            {/* 原生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" 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>
    )
  }
}

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

  1. 写法不同:
    一般组件:<Demo/>
    路由组件:<Route path="/demo" component={Demo}/>
  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"

3.6 NavLink与封装NavLink

NavLink可以实现路由链接的高亮,通过activeClassName属性指定样式名,默认是"active"

<NavLink activeClassName="demo" className="list-group-item" to="/home">Home</NavLink>

可以自己封装一个NavLink【一般组件】

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

export default class MyNavLink extends Component {
  render() {
	// console.log(this.props);
	return (
	  <NavLik activeClassName="demo" className="list-group-item" {...this.props} />
	)
  }
}

标签体内容是特殊的标签属性通过this.props.children可以获取标签体内容
使用

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

3.7 Switch的使用

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

这样只要匹配到了第一个就不会再往下匹配了

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

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

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

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
  2. 开启严格匹配:<Route exact={true} path="/about" component={About}/>
  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

3.10 Redirect的使用 【重定向】

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
  2. 具体编码:
<Switch>
	<Route path="/about" component={About}/>
	<Route path="/home" component={Home}/>
	<Redirect to="/about"/>
</Switch>

4. 嵌套路由使用

4.1 效果

在这里插入图片描述

4.2 注意

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

4.3 实现

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

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

5.1 效果

在这里插入图片描述

5.2 具体方法

方法1. params参数

  1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
  2. 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
  3. 接收参数:this.props.match.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. search参数

  1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.search
  4. 备注:获取到的searchurlencoded编码字符串,需要借助querystring解析
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}

方法3. state参数

  1. 路由链接(携带参数):<Link to={{ pathname:'/demo/test', state:{name:'tom',age:18} }}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.state
  4. 备注:刷新也可以保留住参数【history对象记录着在】

代码

Message/index.jsx

export default class Message extends Component {
  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> */}

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

                  {/* 向路由组件传递state参数 */}
                  <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 声明接收params参数 */}
        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

        {/* search参数无需声明接收,正常注册路由即可 */}
        {/* <Route path="/home/message/detail" component={Detail}/> */}

        {/* state参数无需声明接收,正常注册路由即可 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Detail/index.jsx

import React, { Component } from 'react'
// import qs from 'querystring'

export default class Detail extends Component {
  render() {
    console.log(this.props);

    // 接收params参数
    // const {id,title} = this.props.match.params 

    // 接收search参数
    // const {search} = this.props.location
    // const {id,title} = qs.parse(search.slice(1))

    // 接收state参数
    const {id,title} = this.props.location.state || {}

    const findResult = DetailData.find((detailObj)=>{
      return detailObj.id === id
    }) || {}
    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
        <li>CONTENT:{findResult.content}</li>
      </ul>
    )
  }
}

6. 多种路由跳转方式

6.1 编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()
- this.prosp.history.replace()
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go()

6.2 withRouter的使用

export default withRouter(Header)

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件

7. 注意

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可以用于解决一些路径错误相关的问题。

以上是关于React-路由 react-router-dom的主要内容,如果未能解决你的问题,请参考以下文章

react-router-dom 无法按预期使用路由

ReactSPA - 路由机制 - react-router-dom - 基本路由 - 嵌套路由 - 传递参数 - 路由跳转

React-路由 react-router-dom

React开发中使用react-router-dom路由最新版本V5.1.2路由嵌套子路由

React开发中使用react-router-dom路由最新版本V5.1.2路由嵌套子路由

react-router-dom路由switch使用详解