React学习笔记---React路由(v5带部分v6)

React路由

  • 一、SPA的理解
  • 二、路由的理解
  • 三、react-router-dom
    • 1、理解
    • 2、相关API
  • 四、路由的基本使用
  • 五、NavLink的使用(v5和v6有区别)
  • 六、对NavLink二次封装
  • 七、Switch的使用(v6已经移除switch,替换为Routes)
  • 八、解决多级路径刷新页面样式丢失的问题
  • 九、路由的严格匹配与模糊匹配
  • 十、Redirect的使用(v6版本已经废除,用Navigate 代替)
  • 十一、嵌套路由(v5和v6使用起来不一样)
  • 十二、向路由组件传递params参数
  • 十三、向路由组件传递search参数
  • 十四、向路由组件传递state参数
  • 十四、push与replace模式
  • 十五、编程式路由导航(v5和v6不同)
  • 十六、withRouter的使用(v6版本已经移除了)
  • 十七、BrowserRouter与HashRouter的区别
  • 十八、 总结

一、SPA的理解

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

二、路由的理解

  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组件

    3. 前端路由的基石

      DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>前端路由的基石_historytitle>
      head>
      <body>
      	<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1a><br><br>
      	<button onClick="push('/test2')">push test2button><br><br>
      	<button onClick="replace('/test3')">replace test3button><br><br>
      	<button onClick="back()"><= 回退button>
      	<button onClick="forword()">前进 =>button>
      
      	<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js">script>
      	<script type="text/javascript">
      		// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
      		let history = History.createHashHistory() //方法二,hash值(锚点)
      
      		function push (path) {
      			history.push(path)
      			return false
      		}
      
      		function replace (path) {
      			history.replace(path)
      		}
      
      		function back() {
      			history.goBack()
      		}
      
      		function forword() {
      			history.goForward()
      		}
      
      		history.listen((location) => {
      			console.log('请求路由路径变化了', location)
      		})
      	script>
      body>
      html>
      

三、react-router-dom

1、理解

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

2、相关API

1)、内置组件

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

2)、其它

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

四、路由的基本使用

用实例进行解释,知识点全在注释中
React学习笔记---React路由(v5带部分v6)_第1张图片
index.jsx

//引入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 './components/Home'
import About from './components/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中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在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">
                {/* 注册路由,v6版本需要用routes包裹route,并且component变为了element,element里是一个组件,path引号里的路径不需要'/' */}
                <Route path="/about" component={About} />
                <Route path="/home" component={Home} />
                {/* v6版本这样写 */}
                {/* 
                  } />
                  } />
                 */}
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

About.jsx

import React, { Component } from 'react'

export default class About extends Component {
	render() {
		return (
			<h3>我是About的内容</h3>
		)
	}
}

Home.jsx

import React, { Component } from 'react'

export default class Home extends Component {
	render() {
		return (
			<h3>我是Home的内容</h3>
		)
	}
}

五、NavLink的使用(v5和v6有区别)

目录结构
React学习笔记---React路由(v5带部分v6)_第2张图片
index.jsx

//引入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 { NavLink, Route } from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //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">

              {/* 原生html中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
              <NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
              {/* 
                v6版本不能这样写,V6版本NavLink移除了activeClassName 和activeStyle  
                 ({ color: isActive ? '#FFF' : '#333', backgroundColor: isActive ? 'orange' : 'none' })} className="list-group-item" to="/about">About
                 ({ color: isActive ? '#FFF' : '#333', backgroundColor: isActive ? 'orange' : 'none' })} className="list-group-item " to="/home">Home
                或者这样写
                 (isActive ? " afei" : "list-group-item")} to="/about">About
                 (isActive ? " afei" : "list-group-item")} to="/home">Home
                注意:这里的afei是我自己定义的类名,list-group-item是bootstrap的,活跃状态就显示我定义的
                或者自己封装一个NavLink组件
              */}
            </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>
    )
  }
}

components下的Header下的index.jsx

import React, { Component } from 'react'

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

pages下的About下的index.jsx

import React, { Component } from 'react'

export default class About extends Component {
	render() {
		// console.log('About组件收到的props是',this.props);
		//v6版本收不到
		return (
			<h3>我是About的内容</h3>
		)
	}
}

pages下的Home下的index.jsx

import React, { Component } from 'react'

export default class Home extends Component {
	render() {
		return (
			<h3>我是Home的内容</h3>
		)
	}
}

六、对NavLink二次封装

其他代码和上面都一样,只有下面的与上面的有所不同

在components里新增加一个MyNavLink文件夹及组件
MyNavLink下的index.jsx

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

export default class MyNavLink extends Component {
  render () {
    // console.log(this.props);
    return (
      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props} />
      /* v6版本这样写 */
      //      (isActive ? " afei" : "list-group-item")} {...this.props} />
      //     或者这样写
      //     < NavLink style = {({ isActive }) => ({ color: isActive ? '#FFF' : '#333', backgroundColor: isActive ? 'orange' : '#fff' })
      // } className = "list-group-item" {...this.props } />
    )
  }
}

App.jsx改为这样

import React, { Component } from 'react'
import {Route} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

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

							{/* 原生html中,靠跳转不同的页面 */}
							{/* About
							Home */}

							{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
							<MyNavLink to="/about">About</MyNavLink>
							<MyNavLink to="/home">Home</MyNavLink>
						</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>
		)
	}
}

其余的组件与上面完全一样

七、Switch的使用(v6已经移除switch,替换为Routes)

还是上面的例子
只修改App.jsx

import React, { Component } from 'react'
import {Route,Switch} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'
import Test from './pages/Test'

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

							{/* 原生html中,靠跳转不同的页面 */}
							{/* About
							Home */}

							{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
							<MyNavLink to="/about">About</MyNavLink>
							<MyNavLink to="/home">Home</MyNavLink>
						</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}/>
									<Route path="/home" component={Test}/>
								</Switch>
                {/* 
                v6版本移除了switch,要这样使用
                
									}/>
									}/>
									}/>
								
                 */}
							</div>
						</div>
					</div>
				</div>
			</div>
		)
	}
}

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

上面的例子变动一个地方,就会出现样式丢失的问题
React学习笔记---React路由(v5带部分v6)_第3张图片
把之前的一级路由变为二级路由
第一次加载页面,一切正常
React学习笔记---React路由(v5带部分v6)_第4张图片
可以看到,bootstrap也成功加载
bootstrap在index.html引入的
React学习笔记---React路由(v5带部分v6)_第5张图片
bootstrap的请求路径为
React学习笔记---React路由(v5带部分v6)_第6张图片
然后当你点击刷新的时候,就发现出问题了,样式丢失了
React学习笔记---React路由(v5带部分v6)_第7张图片
正如上图,bootstrap返回的是index.html,再看一下bootstrap的请求路径
React学习笔记---React路由(v5带部分v6)_第8张图片
好家伙,/css前面多了一个/afei,那按道理路径都错了,是不是应该返回404,但是通过结果观察,返回的却是丢失样式的index.html,那是因为react默认,如果找不到public下对应的路径,就返回index.html

那么该如何解决这个样式丢失问题呢?有三种办法
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter,不使用BrowserRouter

通过以上办法解决后,可以发现之后无论你怎样刷新,请求路径始终一样,为
在这里插入图片描述

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

V6版本中exact属性已被移除,所有的路由都是“严格匹配”
看一下v5版本的严格匹配与模糊匹配
还是上面的例子,只有App.jsx发送变化

import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

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

              {/* 原生html中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <MyNavLink to="/about">About</MyNavLink>
              {/* 模糊匹配,虽然有home后面有/a/b,但不影响下面直接匹配/home,注意:这个也不是随便写的。例如写成这样就不行了,/a/home/b */}
              <MyNavLink to="/home/a/b">Home</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Switch>
                 {/*exact代表开启严格模式  */}
                  <Route exact path="/about" component={About} />
                  <Route exact path="/home" component={Home} />
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

十、Redirect的使用(v6版本已经废除,用Navigate 代替)

还是上面的例子
只改变App.jsx

import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

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

              {/* 原生html中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <MyNavLink to="/about">About</MyNavLink>
              <MyNavLink to="/home">Home</MyNavLink>
            </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" />
                  {/* 
                    v6版本已经移除了Redirect,用Navigate去代替原来的功能,需要这样写
                    } />
                  */}
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

十一、嵌套路由(v5和v6使用起来不一样)

还接着用上面的那个例子
只不过这回新增加了两个组件
React学习笔记---React路由(v5带部分v6)_第9张图片
Mseeage下的index.jsx

import React, { Component } from 'react'

export default class Message 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>
		)
	}
}

News下的index.jsx

import React, { Component } from 'react'

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

Home下的index.jsx有所改变

import React, { Component } from 'react'
import MyNavLink from '../../components/MyNavLink'
import { Route, Switch, Redirect } 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>
              <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>
          {/* 
          v6版本此处不需要注册路由,直接在App.jsx中注册就可以
          V6版本这里只需要写一个
         Outlet可以看作一个“占位符”,在父组件所需要渲染子组件的地方进行占位。
         若父组件中定义的路由完全匹配则会在此处呈现子组件内容,如果没有匹配则不会展现任何内容
         */}
        </div>
      </div>
    )
  }
}

App.jsx

import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

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

              {/* 原生html中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <MyNavLink to="/about">About</MyNavLink>
              <MyNavLink to="/home">Home</MyNavLink>
            </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>
                {/* 
                v6版本需要这样写
                
                    } />
                    } >
                      } />
                      } />
                      } />
                    
                    } />
                
              */}
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

其余代码和上面一致

十二、向路由组件传递params参数

还是之前的例子
在message下新增加Details组件
React学习笔记---React路由(v5带部分v6)_第10张图片
变动的地方
Detail下的index.jsx

import React, { Component } from 'react'

const DetailData = [
  { id: '01', content: '你好,中国' },
  { id: '02', content: '你好,尚硅谷' },
  { id: '03', content: '你好,未来的自己' }
]
export default class Detail extends Component {
  render () {
    console.log(this.props);
    //注意:v6版本无法直接获取props,需要使用一些钩子函数
    // 接收params参数
    const { id, title } = this.props.match.params
    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>
    )
  }
}

/*
v6版本,这个文件这样写
import React, { Component } from 'react'
import { useParams } from 'react-router-dom'
// v6使用class组件。需要封装一下。利用hock组件来获取参数,然后传递给class组件
function myWithRouter (Details) {
  return (props) => {
    return 
} } const DetailData = [ { id: '01', content: '111111111111' }, { id: '02', content: '222222222222' }, { id: '03', content: '33333333333333' } ] class Details extends Component { render () { console.log(this.props); const { id, title } = this.props.params const findResult = DetailData.find(detailObj => { return detailObj.id === id }) return (
  • id:{id}
  • title:{title}
  • content:{findResult.content}
) } } export default myWithRouter(Details) */

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} />
        {/* 
          v6版本这里直接写
          注册组件写在App.jsx
        */}
      </div>
    )
  }
}

App.jsx

import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

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

              {/* 原生html中,靠跳转不同的页面 */}
              {/* About
							Home */}

              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <MyNavLink to="/about">About</MyNavLink>
              <MyNavLink to="/home">Home</MyNavLink>
            </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>
                {/* 
                v6版本这样写
                
                  } />
                  } >
                    } />
                    }>
                      } />
                    
                    } />
                  
                  } />
                
                */}
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

其他都一样

十三、向路由组件传递search参数

还是上面的例子
变动的地方
Detail下的index.jsx

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

const DetailData = [
  { id: '01', content: '你好,中国' },
  { id: '02', content: '你好,尚硅谷' },
  { id: '03', content: '你好,未来的自己' }
]
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))

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

/*
v6版本,这个组件这样写
import React, { Component } from 'react'
import { useSearchParams } from 'react-router-dom'
// v6使用class组件。需要封装一下。利用hock组件来获取参数,然后传递给class组件
function myWithRouter (Details) {
  return (props) => {
    let [searchParams] = useSearchParams();
    const params = {
      id: searchParams.get('id'),
      title: searchParams.get('title')
    }
    return 
} } const DetailData = [ { id: '01', content: '111111111111' }, { id: '02', content: '222222222222' }, { id: '03', content: '33333333333333' } ] class Details extends Component { render () { console.log(this.props); const { id, title } = this.props.params const findResult = DetailData.find(detailObj => { return detailObj.id === id }) return (
  • id:{id}
  • title:{title}
  • content:{findResult.content}
) } } export default myWithRouter(Details) */

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参数 */}
                  {/* {msgObj.title} */}

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

                </li>
              )
            })
          }
        </ul>
        <hr />
        {/* 声明接收params参数 */}
        {/*  */}

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

      </div>
    )
  }
}

其他和上面的一样

十四、向路由组件传递state参数

还是上面的例子
变动的地方
Detail下的index.jsx

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

const DetailData = [
  { id: '01', content: '你好,中国' },
  { id: '02', content: '你好,尚硅谷' },
  { id: '03', content: '你好,未来的自己' }
]
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>
    )
  }
}

/*
v6版本这样写
import React, { Component } from 'react'
import { useLocation } from 'react-router-dom'
// v6使用class组件。需要封装一下。利用hock组件来获取参数,然后传递给class组件
function myWithRouter (Details) {
  return (props) => {
    let location = useLocation();
    console.log(location);

    //这里写成 location.state || {}
    //因为state在刷新后可能为空,所以这里为空时设置为空对象

const stateObj = location.state || {};
const params = {
  id: stateObj.id,
  title: stateObj.title
}
return 
} } const DetailData = [ { id: '01', content: '111111111111' }, { id: '02', content: '222222222222' }, { id: '03', content: '33333333333333' } ] class Details extends Component { render () { console.log(this.props); const { id, title } = this.props.params const findResult = DetailData.find(detailObj => { return detailObj.id === id }) || {} return (
  • id:{id}
  • title:{title}
  • content:{findResult.content}
) } } export default myWithRouter(Details) */

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参数 */}
                  {/* {msgObj.title} */}

                  {/* 向路由组件传递search参数 */}
                  {/* {msgObj.title} */}

                  {/* 向路由组件传递state参数 */}
                  <Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
				  {/*
				  在V6版本中经过测试,在to中写对象传state的方式并没有用。V6版本中在Link组件(包含NavLink、Navigate)中state作为一个单独的属性与to分开。
					{messageObj.title} 
				  */}
                </li>
              )
            })
          }
        </ul>
        <hr />
        {/* 声明接收params参数 */}
        {/*  */}

        {/* search参数无需声明接收,正常注册路由即可 */}
        {/*  */}

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


其他和上面的一样

十四、push与replace模式

push 压栈 留下痕迹 点击后退按钮可以退回刚才的页面
replace 替换 不留痕迹 后退按钮点击不了
默认是push模式,如果想设置replace模式,在Link里加一个replace(默认为true)即可

<Link replace to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>

十五、编程式路由导航(v5和v6不同)

v5版本 借助this.props.history对象上的API对操作路由跳转、前进、后退
v6版本提供了useNavigate来实现前进、后退等操作
还是上面的例子
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' },
    ]
  }

  replaceShow = (id, title) => {
    //replace跳转+携带params参数
    //this.props.history.replace(`/home/message/detail/${id}/${title}`)

    //replace跳转+携带search参数
    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)

    //replace跳转+携带state参数
    this.props.history.replace(`/home/message/detail`, { id, title })
  }

  pushShow = (id, title) => {
    //push跳转+携带params参数
    // this.props.history.push(`/home/message/detail/${id}/${title}`)

    //push跳转+携带search参数
    // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)

    //push跳转+携带state参数
    this.props.history.push(`/home/message/detail`, { id, title })

  }

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

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

  go = () => {
    this.props.history.go(-2)
  }

  render () {
    const { messageArr } = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>

                  {/* 向路由组件传递params参数 */}
                  {/* {msgObj.title} */}

                  {/* 向路由组件传递search参数 */}
                  {/* {msgObj.title} */}

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

									&nbsp;<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
									&nbsp;<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>
                </li>
              )
            })
          }
        </ul>
        <hr />
        {/* 声明接收params参数 */}
        {/*  */}

        {/* search参数无需声明接收,正常注册路由即可 */}
        {/*  */}

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

        <button onClick={this.back}>回退</button>&nbsp;
        <button onClick={this.forward}>前进</button>&nbsp;
        <button onClick={this.go}>go</button>

      </div>
    )
  }
}



//V6版本中则提供了useNavigate来实现前进、后退等操作 
//v6这样写
// import React, { Component } from 'react'
// import { Link, Outlet, useNavigate } from 'react-router-dom'

// function anonyCom (MessCom) {
//   return (props) => {
//     let navigate = useNavigate();
//     console.log(navigate);
//     return 
//   }
// }

// class Messages extends Component {
//   state = {
//     messageArr: [
//       { id: '01', title: '消息1' },
//       { id: '02', title: '消息2' },
//       { id: '03', title: '消息3' }
//     ]
//   }

//   pushShow = (id, title) => {
//     this.props.navigate(
//       '/home/message/detail', { state: { id, title } }
//     )
//   }

//   replaceShow = (id, title) => {
//     this.props.navigate(
//       '/home/message/detail', { replace: true, state: { id, title } }
//     )
//   }

//   back = () => {
//     this.props.navigate(-1);
//   }

//   forward = () => {
//     this.props.navigate(1);
//   }

//   go = () => {
//     this.props.navigate(2);
//   }

//   render () {
//     const { messageArr } = this.state
//     return (
//       
//
    // { // messageArr.map(msgObj => { // return ( //
  • // {/* {msgObj.title} */} // {/* {msgObj.title} */ } // {msgObj.title} // // //
  • // ) // }) // } //
//
// // & nbsp; // & nbsp; // //
// ) // } // } // export default anonyCom(Messages)

其他代码不变

十六、withRouter的使用(v6版本已经移除了)

v6版本
若需要实现在头部组件(一般组件)中如何使用路由组件的相关案例。其实上面使用高阶函数封装就是一直在写一个自定义的withRouter
v5版本
withRouter就是让一般组件具备路由组件所特有的API
还是上面的例子
看一下Header下的index.jsx

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

class Header extends Component {

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

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

	go = ()=>{
		this.props.history.go(-2)
	}

	render() {
		console.log('Header组件收到的props是',this.props);
		return (
			<div className="page-header">
				<h2>React Router Demo</h2>
				<button onClick={this.back}>回退</button>&nbsp;
				<button onClick={this.forward}>前进</button>&nbsp;
				<button onClick={this.go}>go</button>
			</div>
		)
	}
}

export default withRouter(Header)

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

十七、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-router-dom v6版本与v5版本对比及v5版本相关替代方案可以参考这篇文章,个人感觉这个作者写的挺好的
https://www.bilibili.com/read/cv14409269

一、路由的基本使用
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
Demo
3.展示区写Route标签进行路径的匹配

v6版本有所不同
需要在Route外面包裹Routes
并且component变为element,{Demo}变为{}

} />
} />

4.的最外侧包裹了一个

二、路由组件与一般组件

1.写法不同:
	一般组件:<Demo/>
	路由组件:<Route path="/demo" component={Demo}/>
	v6版本这样写,也是一般组件<Route path="demo" element={<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"
		
v6文档里把路由组件默认接受的三个属性给移除了,官网文档里给出的解决方案是使用useNavigate()这个hook,
但是hook只能存在于无状态组件,无法用在类组件中,
官方文档里给出的类组件实现编程式导航的解决方案看这篇文章https://www.jianshu.com/p/5bdfd2fac2cd

三、NavLink与封装NavLink

1.NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
 	<NavLink activeClassName="active" className="list-group-item" to="/about">About</NavLink>
	<NavLink activeClassName="active" className="list-group-item" to="/home">Home</NavLink>
	
    注意:v6版本NavLink移除了activeClassName 和activeStyle 那怎么解决呢?
    可以将函数传递给 style 或 className,这将允许您根据组件的活动状态自定义内联样式或类字符串
    <NavLink style={({ isActive }) => ({ color: isActive ? '#FFF' : '#333', backgroundColor: isActive ? 'orange' : 'none' })} className="list-group-item" to="/about">About</NavLink>
    <NavLink style={({ isActive }) => ({ color: isActive ? '#FFF' : '#333', backgroundColor: isActive ? 'orange' : 'none' })} className="list-group-item " to="/home">Home</NavLink>
    
    或者这样写
    <NavLink className={({ isActive }) => (isActive ? " afei" : "list-group-item")} to="/about">About</NavLink>
    <NavLink className={({ isActive }) => (isActive ? " afei" : "list-group-item")} to="/home">Home</NavLink>
    注意:这里的afei是我自己定义的类名,list-group-item是bootstrap的,活跃状态就显示我定义的或者自己封装一个NavLink组件

四、Switch的使用
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
在V6版本中Switch组件已移除,用Routes代替

五、解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter

六、路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
V6版本中exact属性已被移除,所有的路由都是“严格匹配”

七、Redirect的使用

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
	<Route path="/about" component={About}/>
	<Route path="/home" component={Home}/>
	<Redirect to="/about"/>
</Switch>
v6版本的Redirect已经被删除,用Navigate 代替
<Route path="*" element={<Navigate to="/home" />} />

八、嵌套路由

1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
  v6版本与之有所不同
  在父组件中写好导航路由,即
  <ul className="nav nav-tabs">
     <li>
        <MyNavLink to="/home/news">News</MyNavLink>
      </li>
      <li>
         <MyNavLink to="/home/message">Message</MyNavLink>
      </li>
    </ul>
    <Outlet />
Outlet可以看作一个“占位符”,在父组件所需要渲染子组件的地方进行占位。
若父组件中定义的路由完全匹配则会在此处呈现子组件内容,如果没有匹配则不会展现任何内容
然后在App.jsx中与父组件一起注册路由即可
    <Routes>
      <Route path='about' element={<About />} />
      <Route path='home/*' element={<Home />} >
	      <Route path="news" element={<News />} />
	      <Route path="message" element={<Message />} />
	      <Route path="*" element={<Navigate to="/home/news" />} />
      </Route>
      <Route path="*" element={<Navigate to="/about" />} />
   </Routes>

九、向路由组件传递参数

1.params参数
路由链接(携带参数)<Link to={`/demo/test/${arr.name}/${arr.age}}`>详情</Link>
注册路由(声明接收)<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params

v6版本需要使用useParams才能拿到参数
v6版本接收参数需要这样写

function myWithRouter (Details) {
  return (props) => {
     return <Details params={useParams()} />
  }
}
export default myWithRouter(Details)

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

v6版本需要使用useSearchParams才能拿到参数
function myWithRouter (Details) {
   return (props) => {
   console.log(useSearchParams());
   let [searchParams] = useSearchParams();
   const params = {
      id: searchParams.get('id'),
      title: searchParams.get('title')
   }
   return <Details params={params} />
   }
}
export default myWithRouter(Details)

3.state参数
路由链接(携带参数)<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>

v6版本这样写
<Link to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>

注册路由(无需声明,正常注册即可)<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数

v6版本需要使用useLocation才能拿到参数
function myWithRouter (Details) {
  return (props) => {
     let location = useLocation();
     console.log(location);
 /*
   这里写成 location.state || {}
   因为state在刷新后可能为空,所以这里为空时设置为空对象
 */
    const stateObj = location.state || {};
    const params = {
       id: stateObj.id,
       title: stateObj.title
    }
    return <Details params={params} />
  }
}
 export default myWithRouter(Details)


十、编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
V6版本中则提供了useNavigate来实现前进、后退等操作

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

下一篇redux在这里
redux

你可能感兴趣的:(React专栏,react.js,前端,javascript)