1. SPA的理解
- 单页Web应用(single page web application,SPA)
- 整个应用只有一个完整的页面
- 点击页面的链接不会刷新页面,只会做页面的局部更新
- 数据都需要通过ajax请求获取,并在前端异步展示
2. 路由的理解
1. 什么是路由?
- 一个路由就是一个映射关系(key: value)
- key为路径,value可能是function或component
2. 路由分类
- 后端路由:
- 理解:value是function,用来处理客户端提交的请求
- 注册路由:router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由:
- 浏览器端路由,value是component,用于展示页面内容
- 注册路由:
<Route path="/test" component={Test}>
- 工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
3. react-router-dom的理解
- react的一个插件库
- 专门用来实现一个SPA应用
- 基于react的项目基本都会用到此库
4. 安装react-router-dom
- 默认版本是6,所以安装版本5的时候
npm i react-router-dom@5
3. 路由的基本使用
-
明确好界面中的导航区、展示区
-
导航区的a标签改为Link标签
<Link to="/xxx">Demo</Link>
-
展示区写Route标签进行路径的匹配
<Route path="/xxx" component={Demo}/>
-
<App>
的最外侧包裹了一个<BrowserRouter>
或<HashRouter>
index.jsx
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
// 引入路由
import { BrowserRouter } from 'react-router-dom'
// 渲染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中,靠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>
)
}
}
还有About和Home组件就不写出来了。
用Header组件作为一般组件代替标题,来区分一般组件和路由组件。
Header/index.jsx
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>
)
}
}
- 写法不同:
- 一般组件:
<Demo/>
- 路由组件:
<Route path="/demo" component={Demo}/>
- 一般组件:
- 存放位置不同:
- 一般组件:components
- 路由组件:pages
- 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能接收到什么
- 路由组件:接收到三个固定的属性:
4. NavLink的基本使用
{/* 如果只想给NavLink加active属性,就不用添加activeClassName类名 */}
{/* 然后给类名添加样式 */}
<NavLink activeClassName="fg" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="fg" className="list-group-item" to="/home">Home</NavLink>
5. NavLink与封装NavLink
- NavLink可以实现由链接的高亮,通过activeClassName指定样式名
- 标签体内容是一个特殊的标签属性、
- 通过this.props.children可以获取标签体内容
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
封装的MyNavLink组件
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="fg" className="list-group-item" {...this.props} />
}
}
6. Switch的使用
- 通常情况下,path和component是一一对应的关系
- Switch可以提高路由匹配的效率(单一匹配)
{/* 注册路由*/}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
</Switch>
匹配到一次后,就不再匹配,大大提高效率。
7. 解决多级路径刷新页面导致样式丢失的问题
- public/index.html 中引入样式时不写 ./,直接写 / (常用)
- public/index.html 中引入样式时不写 ./,直接写 %PUBLIC_URL%(常用)
- 使用HashRouter(不常用)
8. 路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
9. Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由。
import { Route, Switch, Redirect } from 'react-router-dom'
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
10. 嵌套路由
- 注册子路由要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
11. 向路由组件传递参数
- params参数
- 路由链接(携带参数):
<Link to='/demo/test/tom/18'>详情</Link>
- 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
- 接收参数:
const {id,title} = this.props.match.params
- 路由链接(携带参数):
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>
</div>
</div>
)
}
}
Home/Message/index.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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>
)
}
}
Home/Message/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)
// 接收params参数
const { id, title } = this.props.match.params
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
<div>
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content:{findResult.content}</li>
</ul>
</div>
)
}
}
- search参数
- 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'>详情</Link>
- 注册路由(无需声明,正常注册路由即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.search
- 注意:获取到的search是urlencoded编码字符串,需要借助querystring解析
- 路由链接(携带参数):
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />
// 引入qs库解析接收的search参数
import qs from 'qs'
// 接收search参数
const { search } = this.props.location
const {id,title} = qs.parse(search.slice(1))
- state参数
- 路由链接(携带参数):
<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
- 注册路由(无需声明,正常注册路由即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.state
- 注意:刷新也可以保留住参数
- 路由链接(携带参数):
{/* 向路由组件传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />
//接收state参数
const { id, title } = this.props.location.state || {}
const findResult =
DetailData.find((detailObj) => {
return detailObj.id === id
}) || {}
12. push与replace
- replace会替换当前历史记录
- 不加replace,默认就是push模式,push会保留历史记录
<MyNavLink replace to="/about">About</MyNavLink>
13. 编程式路由导航
借助this.props.history
对象上的API对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
App.jsx
import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Header from './components/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中,靠a链接跳转页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* React中靠路由链接实现切换组件---编写路由链接 */}
{/* 如果只想给NavLink加active属性,就不用添加activeClassName类名 */}
<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>
</div>
</div>
</div>
</div>
</div>
)
}
}
index.js
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
// 引入路由
import { BrowserRouter } from 'react-router-dom'
// 渲染App到页面
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
Home/Message/index.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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参数*/}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button> */}
{/* 向路由组件传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>
</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} />
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
Home/Message/Detail/index.jsx
import React, { Component } from 'react'
// 引入qs库解析接收的search参数
// import qs from 'qs'
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 (
<div>
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content:{findResult.content}</li>
</ul>
</div>
)
}
}
14. withRouter
- withRouter可以加工一般组件,让一般组件具备组件所特有的API
- withRouter的返回值是一个新组件
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>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
16. BrowserRouter与HashRouter的区别
- 底层原理不一样:
- BrowserRouter使用的H5的history API,不兼容IE9及以下版本
- HashRouter使用的是URL的哈希值
- path表现形式不一样:
- BrowserRouter的路径中没有#,例如:
localhost:3000/demo/test
- HashRouter的路径中包含#,例如:
localhost:3000/#/demo/test
- BrowserRouter的路径中没有#,例如:
- 刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中
- HashRouter刷新后导致路由state参数的丢失
- 注意:HashRouter可以用于解决一些路径错误相关的问题