多页面应用是指每切换一个页面就是一个真正的html页面。
单页面应用是指整个应用只有一个完整的页面,点击页面中的链接不会刷新页面只会局部更新,并且数据都需要通过ajax请求获取,并在前端异步展示。
单页面,多组件。
key:value
)function
,用来处理客户端提交的请求。router.get(path, function(req, res))
路由匹配的是只是端口号后面的内容(/about、/history)。
那么是如何实现一点击导航栏的选项浏览器地址栏中的路径就改变呢, 这就需要借助history实现。
浏览器的BOM身上有一个history,用于管理浏览器的路径、历史记录等。
但是BOM原生的history不好操作我们一般用一个封装好的库history.js
,(该库实际上操作的也是BOM身上的history)引入:
,使用:
let history = History.createBrowserHistory()
// 将路径放到历史记录history中history.push(path)
// 将路径放到历史记录history中history.replace(path)
// history的listen方法可以监听路径的修改history.listen((location) => {console.log('请求路由路径变化了', location)})
history.goForward()
history.goBack()
history的数据结构是栈结构:
栈顶的路径就是当前页面显示的内容。
History.createBrowserHistory()
是使用的是h5身上的history方法,在某些旧的浏览器不支持。
可以使用: History.createHashHistory()
,和createBrowserHistory
的区别是路径中会多出一个 #
,虽然不好看但是兼容性特别好。
History.createBrowserHistory():
History.createHashHistory():
React会监听路径,当路径发生变化时,被前端路由器
检测到,就会进行路由匹配重新渲染页面。
React路由的实现需要借助react的路由插件库:react-router-dom
, 专门用于实现SPA应用。
npm i react-router-dom
直接利用从 react-router-dom
库中引入的路由器Router
和标签
就可以实现:
import { BrowserRouter, Link } from 'react-router-dom'AboutHome
点击About浏览器的路径就变成 /about
, 点击Home浏览器的路径就变成/home
有两种Router标签:
,在Router标签中再使用进行路由。
标签就对应history的browser模式
标签就对应history的hash模式
(最后在浏览器渲染的时候,Link标签实际上是会转换为a标签的)
直接利用从 react-router-dom
库中引入的路由Route
进行路由的注册:
import { Route } from 'react-router-dom'
{/* 注册路由 */}About}/>Home}/>
也需要使用Router标签进行包裹,但是编写路由链接的路由器应该和注册路由的路由器是一个路由器才对,可以直接将
组件使用Router标签包裹即可。
index.js
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { BrowserRouter } from 'react-router-dom'
// 引入App组件
import App from './App'// 渲染App到页面
ReactDOM.render( , document.getElementById('root'))
像上面的例子通过路由来使用组件:
,这样的组件叫做路由组件。还有一类就是通过标签正常使用的组件如
,这样的组件就是普通组件。
普通组件一般直接放在src/component
文件夹中,路由组件一般放在src/pages
中。
普通组件和路由组件一个最大的区别就是,路由组件可以接收到路由器
传递的参数。如当点击上述例子的About按钮时,接收到的参数为:
|n|
步,n>0表示前进n步,n<0表示后退n步。实现效果:
代码:
src/index.js
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { BrowserRouter } from 'react-router-dom'
// 引入App组件
import App from './App'// 渲染App到页面
ReactDOM.render( , document.getElementById('root'))
src/App.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
export default class App extends Component {render() {return ()}
}
page/About/index.jsx
import React, { Component } from 'react'export default class About extends Component {render() {console.log("接收到参数:", this.props);return (About ... )}
}
page/Home/index.jsx
import React, { Component } from 'react'export default class Home extends Component {render() {return (Home ... )}
}
components/Header/index.jsx
import React, { Component } from 'react'export default class index extends Component {render() {return (React Router Demo
)}
}
需要借助
实现,
标签配合 activeClassName='类名'
实现,当点击该标签 该样式就会显示。
activeClassName='类名'
中的类名默认是active。
eg:
上述案例修改App.jsx
import React, { Component } from 'react'
import { NavLink, Route } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
export default class App extends Component {render() {return (About Home {/* 注册路由 */}About}/>Home}/> )}
}
效果:
注意:这里我在public文件夹中已经引入了bootstrap.css样式,所以会有样式效果
知识点:
children属性
传递给组件即:
App.jsx
import React, { Component } from 'react'
import { Route } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {render() {return ( {/* 标签体中的内容会作为children属性传递给组件 */}Home About {/* 注册路由 */}About}/>Home}/> )}
}
MyNavLink,jsx
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'export default class MyNavLink extends Component {render() {return (...this.props}/>)}
}
一般情况下一个路径(path)只对应一个组件,如果一个路径对应多个组件会是什么效果呢,答案是多个组件都会显示,如:
{/* 注册路由 */}About}/>Home}/>Test}/>
显示如下:
说明当路径匹配完成之后如果再出现同样的路径就会再次匹配,但是一般情况下一个路径对应一个组件就可以了,所以说为了提升效率,我们希望当匹配到一个路径之后,再碰到该路径就不会进行匹配了。这就可以借助
组件,用
组件包裹路由。
import { Route, Switch } from 'react-router-dom'{/* 注册路由 */}About}/>Home}/>Test}/>
启动React脚手架,浏览器访问http://localhost:3000/favicon.ico
,代表访问public文件夹下的favicon.ico资源。即本地服务器的根路径就是/public文件夹
,如果/public文件夹中没有对应的资源就会返回/public/index.html
内容。
当路由路径是多级路径,并且刷新页面的时候就会出现样式丢失。因为他会把一级路由放在请求路径进行请求。
eg:
app.jsx
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {render() {return ( {/* 标签体中的内容会作为children属性传递给组件 */}Home About {/* 注册路由 */}About}/>Home}/> )}
}
浏览器:
%PUBLIC_URL%
,%PUBLIC_URL%
代表的是public文件夹的绝对路径
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { HashRouter } from 'react-router-dom'
// 引入App组件
import App from './App'// 渲染App到页面
ReactDOM.render( , document.getElementById('root'))
一般情况下:NavLink的
to 属性和Route
的path
属性匹配到的路径是一样的。
当 NavLink
的 to
属性是多级路由,但是Route
的path
只是匹配到第一级路由,那么组件会进行展示;
但是如果当 NavLink
的 to
属性是一级路由,但是Route
的path
匹配的是多级路由,组件是不会进行展示的。
这就是模糊匹配。
eg:
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {render() {return ( {/* 标签体中的内容会作为children属性传递给组件 */}Home About {/* 注册路由 */}Home}/>About}/> )}
}
组件正常显示:
但是如果是:
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {render() {return ( {/* 标签体中的内容会作为children属性传递给组件 */}Home About {/* 注册路由 */}Home}/>About}/> )}
}
组件就不展示了。
就是NavLink的
to 属性和Route
的path
属性匹配到的路径必须是一样的。
开启严格匹配的方式:给
标签添加 exact
属性。
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {render() {return ( {/* 标签体中的内容会作为children属性传递给组件 */}Home About {/* 注册路由 */}true} path="/home/a/b" component={Home}/>true} path="/about/c/d" component={About}/> )}
}
严格匹配不要随便开启,可能会导致二级路由无法使用。
当Route指明的所有路由都匹配不上的时候,就匹配Redirect中的路由
。
使用:Redirect在所有Route标签之后使用,利用to属性指明路由地址。
{/* 注册路由 */}Home}/>About}/>
嵌套路由就是在路由组件中再进行路由的导航和注册。
eg:效果:
新增和改动的页面:
Home/index.jsx
import React, { Component } from 'react'
import {Switch, Route, Redirect} from 'react-router-dom'import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Message from './Message'export default class Home extends Component {render() {return (我是Home组件
News Message
{/* 注册路由 */}News}/>Message}/> )}
}
Home/News/index.jsx
import React, { Component } from 'react'export default class News extends Component {render() {return (- news001
- news002
- news003
)}
}
Home/Message/index.jsx
import React, { Component } from 'react'export default class Message extends Component {render() {return ()}
}
Home/index.js中的
匹配的路由就是二级路由,注意这里的路由匹配规则不是直接匹配。所有的路由匹配会按照注册路由的顺序进行匹配,
会先匹配App.jsx中的注册路由,匹配到了
之后再去Home组件匹配注册路由,当匹配到了
就在页面进行显示 。
Home/index.js中的
, 就是设置当只匹配到了/home
, 后面的路径未匹配时在Home页面默认显示的内容。
总结:
`/home/message/detail/001/消息1`}>{message.title}
:参数名
进行接收)Detail}/>
this.props.match.params
中:render() {const {id, title} = 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:'001', title:'消息1'},{id:'002', title:'消息2'},{id:'003', title:'消息3'}] }render() {const {messageArr} = this.statereturn ({messageArr.map((message) => {return (- message.id}>{/* 向路由组件传递params参数 */}`/home/message/detail/${message.id}/${message.title}`}>{message.title}
)})}
{/* 注册路由 */}{/* 接收路由组件传递的params参数 */}Detail}/> )}
}
Detail/index.jsx
import React, { Component } from 'react'export default class Detail extends Component {state = {dataDetail:[{id: '001', context: '我是消息1的内容'},{id: '002', context: '我是消息2的内容'},{id: '003', context: '我是消息3的内容'},]}render() {const {id, title} = this.props.match.paramsconst {dataDetail} = this.state const findDetail = dataDetail.find((detailObj) => {return detailObj.id === id})return (ID: {id}TITLE: {title}Context:{findDetail.context})}
}
stringify()方法
将 对象格式转换为urlencoded编码格式 (key=value&key=value
)import qs from 'qs'let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));
输出:
name=yang&age=20
parse()方法
将urlencoded编码格式 转换为对象格式console.log(qs.parse('name=yang&age=20'))
输出:
{name: 'yang', age: '20'}
`/home/message/detail?id=${message.id}&title=${message.title}`}>{message.title}
Detail}/>
this.props.location.search
中: render() {// 接收search参数const {search} = this.props.location// slice(1)是为了去掉参数传递时的问号const {id, title} = qs.parse(search.slice(1))}
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:'001', title:'消息1'},{id:'002', title:'消息2'},{id:'003', title:'消息3'}] }render() {const {messageArr} = this.statereturn ({messageArr.map((message) => {return (- message.id}>{/* 向路由组件传递params参数 */}{/* {message.title} */}{/* 向路由组件传递search参数 */}`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}
)})}
{/* 注册路由 */}{/* 接收路由组件传递的params参数 */}{/* */}{/* search参数无需接受 */}Detail}/> )}
}
Detail/index.jsx
import React, { Component } from 'react'
import qs from 'qs'let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));console.log(qs.parse('name=yang&age=20'))export default class Detail extends Component {state = {dataDetail:[{id: '001', context: '我是消息1的内容'},{id: '002', context: '我是消息2的内容'},{id: '003', context: '我是消息3的内容'},]}render() {// 接收params参数// const {id, title} = this.props.match.params// 接收search参数const {search} = this.props.locationconst {id, title} = qs.parse(search.slice(1))const {dataDetail} = this.state const findDetail = dataDetail.find((detailObj) => {return detailObj.id === id})return (ID: {id}TITLE: {title}Context:{findDetail.context})}
}
params参数和search参数都会显示在地址栏中,但是state参数就不会显示在地址栏上
{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}
Detail}/>
this.props.location.state
中,是对象格式 render() {const {id, title} = this.props.location.state }
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:'001', title:'消息1'},{id:'002', title:'消息2'},{id:'003', title:'消息3'}] }render() {const {messageArr} = this.statereturn ({messageArr.map((message) => {return (- message.id}>{/* 向路由组件传递params参数 */}{/* {message.title} */}{/* 向路由组件传递search参数 */}{/* {message.title} */}{/* 向路由传递state参数 */}{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}
)})}
{/* 注册路由 */}{/* 接收路由组件传递的params参数 */}{/* */}{/* search参数无需接受 */}{/* */}{/* state参数无需接受 */}Detail}/> )}
}
Message/Detail/index.jsx
import React, { Component } from 'react'
import qs from 'qs'let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));console.log(qs.parse('name=yang&age=20'))export default class Detail extends Component {state = {dataDetail:[{id: '001', context: '我是消息1的内容'},{id: '002', context: '我是消息2的内容'},{id: '003', context: '我是消息3的内容'},]}render() {// 接收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 {dataDetail} = this.state const findDetail = dataDetail.find((detailObj) => {return detailObj.id === id}) || {}return (ID: {id}TITLE: {title}Context:{findDetail.context})}
}
浏览器地址中没有相关参数信息,但是页面刷新的时候参数不会丢失因为state参数存储在history的location的state
中。
state参数是存储在路由组件的history的location的state
中的,所以当浏览器的缓存被清空,history就被清空,参数就会丢失,再次访问该地址就无法获取参数就会报错。
浏览器的history会存储我们访问的路径,存储结构是栈结构,存储模式有两种模式push模式和replace模式:
默认是push模式
,可以通过给Link标签添加replace
属性即可:
true} to={{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}
可以不通过Link标签
来实现路由导航的路由是编程时路由导航,实际上是操作history身上的API。
注意:只有路由组件才有.props.history属性,才能直接使用如下变成式路由导航
this.props.history
的API:
replace和push方法可以携带不同的参数(param、query、state)进行路由跳转。
replace跳转
// replace + params参数this.props.history.replace(`/home/message/detail/${id}/${title}`)// replace + query参数this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)// replace + state参数this.props.history.replace(`/home/message/detail`, {id, title})
push跳转
// push + params参数this.props.history.push(`/home/message/detail/${id}/${title}`)// push + query参数this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)// push + state参数this.props.history.push(`/home/message/detail`, {id, title})
eg:
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:'001', title:'消息1'},{id:'002', title:'消息2'},{id:'003', title:'消息3'}] }replaceShow = (id, title)=>{// replace + params参数// this.props.history.replace(`/home/message/detail/${id}/${title}`)// replace + query参数// 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 + query参数// 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.statereturn ({messageArr.map((message) => {return (- message.id}>{/* 向路由组件传递params参数 */}{/* {message.title} */}{/* 向路由组件传递search(query)参数 */}{/* {message.title} */}{/* 向路由传递state参数 */}{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}
)})}
{/* 注册路由 */}{/* 接收路由组件传递的params参数 */}{/* */}{/* search参数无需接受 */}{/* */}{/* state参数无需接受 */}Detail}/> )}
}
message/detail/index.jsx
不同的参数接收方式也要做出相应的改变
import React, { Component } from 'react'
import qs from 'qs'let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));console.log(qs.parse('name=yang&age=20'))export default class Detail extends Component {state = {dataDetail:[{id: '001', context: '我是消息1的内容'},{id: '002', context: '我是消息2的内容'},{id: '003', context: '我是消息3的内容'},]}render() {// 接收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 {dataDetail} = this.state const findDetail = dataDetail.find((detailObj) => {return detailObj.id === id}) || {}return (ID: {id}TITLE: {title}Context:{findDetail.context})}
}
注意:由于一般组件身上没有.props.history属性,但是如果还是想要实现编程式路由导航可以借助withRouter实现
withRouter可以加工一般组件,让一般组件具备路由组件特有的API(props.history等)
withRouter的返回值是一个带有路由组件API的新组件。
使用:
Header/index.jsx(Header组件是一个一般组件)
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() {return (React Router Demo
)}
}export default withRouter(Header)
// withRouter可以加工一般组件,让一般组件具备路由组件特有的API(props.history等)
H5的history API
(注意不是组件实例身上的history),不兼容IE9及以下版本。URL的哈希值
。没有#
,例如: localhost:3000/demo/test包含#
,例如: localhost:3000/#/demo/testURL的哈希值
(即#后面的值)传给服务器。