路由
是一个比较广义和抽象的概念,路由的本质
就是一个映射关系(key: value
),请求什么响应什么,key
为路径,value
可能为function或component,在开发中路由分为前端路由
和后端路由
概念
:根据不同的用户事件,显示不同的页面内容,value为component
注册路由:
当浏览器的path
变为/test
时, 当前路由组件就会变为Test
组件
<Route path="/test" component={Test}>
本质
:用户事件与事件处理函数之间的对应关系
概念
:根据不同的URL地址分发不同的资源,value为function
注册路由
: 当node
接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
router.get(path, function(req, res))
本质
:URL 请求地址与服务器资源之间的对应关系
由于后端渲染存在性能问题以及Ajax前端渲染不支持浏览器的前进后退操作,所以出现SPA技术
SPA(Single Page Application)单页面应用程序
:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作,只有第一次会加载页面,以后的每次请求,仅仅是获取必要的数据,然后由页面中js解析获取的数据进行展示。
实现前端路由
:基于URL中的hash实现,hash的变化会导致浏览器记录访问历史的变化、但是hash的变化不会触发新的URL请求,点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换。
react-router-dom:
React
的一个插件库React
的项目基本都会用到此库React Router中有三类内置组件:
、
)
、
)
、
、
)其它:
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:
安装完成后,上面所列出的这些组件,我们可以通过react-router-dom得到。
import { BrowserRouter, Route, Link } from "react-router-dom";
基于React Router的web应用,根组件应该是一个router组件(BrowserRouter、HashRouter)。 react-router-dom
提供了两种路由,两种路由都会创建一个history对象。如果我们的应用有服务器响应web的请求,我们通常使用
组件;如果使用静态文件服务器,则我们应该使用
组件。
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(局部更新)
路由需要实现三个功能:
在单页面web网页中,单纯的浏览器地址改变,网页不会重载,如单纯的hash值改变,网页是不会变化的,因此我们的路由需要监听事件,并利用js实现动态改变网页。
react-router路由模式:
hash 模式
:监听浏览器地址hash值变化,并执行相应的js切换。history 模式
:利用H5 history API实现url地址改变,网页内容改变。使用window.location.hash
属性和window.onhashchange
事件,可以监听浏览器hash值的变化,去执行相应的js切换网页。
hash路由实现原理:
散列值
。我们使用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
才会触发该事件。react-router-dom中有两个匹配路由的组件:
和
import { Route, Switch } from "react-router-dom";
路由匹配
是通过将
与当前的location的pathname
进行比较来完成的。 当一个
匹配了 ,它所对应的组件内容将被渲染出来。 不匹配,则渲染null。如果一个
没有path属性,他的组件对应内容将一直被渲染出来。
// 当 location = { pathname: '/about' }
<Route path='/about' component={About}/> // 路径匹配成功,渲染 组件
<Route path='/contact' component={Contact}/> // 路径不匹配,渲染 null
<Route component={Always}/> // 该组件没有path属性,其对应的 组件会一直渲染
我们可以在组件树的任何位置放置
组件。但是更常见的情况是将几个写在一起。
组件可以用来将多个
“包裹”在一起。
多个组件在一起使用时,并不强制要求使用
组件,但是使用
组件却是非常便利的,可以提高路由匹配效率(单一匹配)。
会迭代它下面的所有
子组件,并只渲染第一个路径匹配的
,这样只要匹配到了第一个就不会再往下匹配了。
<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的路径都没有匹配上,则 被渲染,我们可以在此组件中返回404 */ }
<Route component={NoMatch} />
</Switch>
匹配时所显示的组件,有三种写法:
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>}/>
React Router提供了一个组件用来在应用中添加link。 当渲染时, 一个
标签在html页面中被创建出来。
<Link to="/">Home</Link>
// Home
组件是一个特殊的组件。当它的path与当前location匹配时,可以自定义其样式来表示当前页面位置。NavLink可以实现路由链接的高亮,通过
activeClassName
属性指定样式名,默认是"active"
// location = { pathname: '/react' }
<NavLink to="/react" activeClassName="hurray">
React
</NavLink>
// React
当需要页面重定向时,我们可以使用
组件。当一个组件被渲染时,页面将被渲染到
组件的to
属性指定的位置上。
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/dashboard"/>
) : (
<PublicHomePage/>
)
)}/>
效果图
路由的使用原理:在导航区
点击导航链接引起路径的变化,路径的变化被路由器检测到进行匹配组件,在展示区
进行展示
路由的基本使用:
导航区
、展示区
渲染时,一个
标签在html页面中被创建出来。Demo
的最外侧包裹了一个
或
代码实现
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>
)
}
}
写法不同:
一般组件:
路由组件:
存放位置不同:
一般组件: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_URL%
(常用)HashRouter
push模式与replace模式:
//push模式(默认):可以留下操作记录,不发生替换
//replace模式:开启的路由不会留下操作记录,发生替换
<Link replace to="/about">About</Link>
效果图
注意:
代码实现
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>
)
}
}
效果图
向路由组件传递params参数:
详情
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>
)
}
}
向路由组件传递search参数:
路由链接(携带参数):详情
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.search
注:获取到的search
是urlencoded
编码字符串,形式为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 './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}>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}> {msgObj.title}
</Link>
</li>
)
})
}
</ul>
<hr />
{/* search参数无需声明接收,正常注册路由即可 */}
<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 {
state = {
detailData : [
{ id: '01', content: '你好啊' },
{ id: '02', content: '还不错鸭' },
{ id: '03', content: '显示我吧' }
]
}
render() {
// 接收search参数
const { search } = this.props.location.search
const {id,title}=qs.parse(search.slice(1))//slice(1)去掉问号
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>
)
}
}
向路由组件传递state参数:
{ pathname:'/demo/test', state:{name:'tom',age:18} }}>详情
this.props.location.state
注:刷新也可以保留住参数【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}>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}> {msgObj.title}
</Link>
</li>
)
})
}
</ul>
<hr/>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
Detail/index.jsx
import React, { Component } from 'react'
export default class Detail extends Component {
render() {
// 接收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>
)
}
}
借助this.props.history
对象上的API对操作路由跳转、前进、后退
this.props.history.push( ):跳转留下记录
this.props.history.replace( ):跳转不留记录
//push跳转 分别携带params、search、state参数
this.props.history.push(`home/message/detail/&{id}/&{title}`)
this.props.history.push(`home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`home/message/detail`,{id,titel})
//replace跳转 携带params参数
this.props.history.replace(`home/message/detail/&{id}/&{title}`)
this.props.history.goBack( )
this.props.history.goForward( )
this.props.history.go(n):默认n=0,刷新当前界面, n=-1相当于goBack( ),n=1相当于goForward( )
import {withRouter} from 'react-router-dom'
clss Header extends Compnent {
...
}
export default withRouter(Header)
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的API,withRouter
的返回值是一个新组件