React 路由
相关理解
SPA的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取, 并在前端异步展现。
路由的理解
什么是路由?
- 一个路由就是一个映射关系(
key:value
) - key为路径, value可能是
function
或component
路由分类
后端路由
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由:
router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由:
<Route path="/test" component={Test}>
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
react-router-dom的理解
1
| yarn add react-router-dom
|
相关概念
- react的一个插件库。
- 专门用来实现一个SPA应用。
- 基于react的项目基本都会用到此库。
相关api
1、内置组件
<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
2、其他
- history对象
- match对象
- withRouter函数
路由的基本使用
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
1
| <Link to="/xxxxx">Demo</Link>
|
3.展示区写Route标签进行路径的匹配
1
| <Route path='/xxxx' component={Demo}/>
|
4.<App>
的最外侧包裹了一个<BrowserRouter>或<HashRouter>
1 2 3 4 5 6
| ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.getElementById('root') )
|
路由组件与一般组件
- 写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
- 存放位置不同:
一般组件:components
路由组件:pages
- 接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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"
|
NavLink使用与封装
- NavLink可以实现路由链接的高亮,通过
activeClassName
指定样式名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import {NavLink,Route} from 'react-router-dom' {} { }
{} <NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink> <NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink> <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>react脚手架</title> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="stylesheet" href="/css/bootstrap.css"> <style> .atguigu{ background-color: rgb(209, 137, 4) !important; color: white !important; } </style> </head> <body> <div id="root"></div> </body> </html>
|
- 封装
1 2 3 4 5 6 7 8
| export default class MyNavLink extends Component { render() { return ( <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/> ) } }
|
- 使用与调用
1 2
| <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink>
|
注:标签体内容也是一种特殊的标签属性
1 2
| <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/about" children="About"/>
|
Switch的使用
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配) —- 即匹配到一个后将不再往下匹配
1 2 3 4 5
| <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> </Switch>
|
解决多级路径刷新页面样式丢失的问题
1.public/index.html
中 引入样式时不写 ./
写 /
(常用)./
相对路径
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL%
(常用,但只在react中
有效果)绝对路径
3.使用HashRouter
(不常用) #
后面自动忽略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>react脚手架</title> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="stylesheet" href="/css/bootstrap.css"> </head> <body> <div id="root"></div> </body> </html>
|
补充:yarn start
和npm start
都可,但安装包时尽量用一种
路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true}path="/about" component={About}/>
可以省略exact={true}
为exact
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
1 2 3 4 5 6 7
| <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home/a/b">Home</MyNavLink>
<Switch> <Route exact path="/about" component={About}/> <Route exact path="/home" component={Home}/> </Switch>
|
Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
1
| import {Route,Switch,Redirect} from 'react-router-dom'
|
- 具体编码:
1 2 3 4 5
| <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
|
嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| -------------------注册一级路由----------------------------- {} <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> {} <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch> ----------------------注册二级路由 :Home组件----------------------------------- <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>
|
向路由组件传递参数
params参数
- 路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
- 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
- 接收参数:
this.props.match.params
(子组件)
1 2 3 4 5 6 7 8 9 10
| -------------------------------发送参数:父组件---------------------------------------------- <div> {/* 向路由组件传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> <hr /> {/* 声明接收params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail} /> </div> --------------------------------接受参数:子组件----------------------------------------------------------- const {id,title} = this.props.match.params
|
优势: 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
search参数
- 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
- 注册路由(
无需声明
,正常注册即可):<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.search
- 备注:获取到的search是
urlencoded编码字符串
,需要借助querystring解析
1 2 3 4 5 6 7 8 9 10 11 12 13
| -------------------------------发送参数:父组件---------------------------------------------- <div> {/* 向路由组件传递search参数 */} <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> <hr /> {/* search参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail}/> </div> --------------------------------接受参数:子组件----------------------------------------------------------- import qs from 'querystring'
const {search} = this.props.location const {id,title} = qs.parse(search.slice(1))
|
优势: 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
state参数
- 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
- 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.state
- 备注:使用
BrowserRouter
刷新才可以保留住参数
,使用HashRouter
刷新后state将会没有history
来保存参数 - 子组件接受参数时
const {id,title} = this.props.location.state || {}
,后面添加||{}
是防止state为undefined时报错
1 2 3 4 5 6 7 8 9 10 11
| -------------------------------发送参数:父组件---------------------------------------------- <div> {/* 向路由组件传递state参数 */} <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title} </Link> <hr /> {/* state参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail}/> </div> --------------------------------接受参数:子组件-----------------------------------------------------------
const {id,title} = this.props.location.state || {}
|
优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏, HashRouter可能会丢失参数
编程式路由导航
借助this.prosp.history
对象上的API对操作路由跳转、前进、后退
this.prosp.history.push()
将历史记录压入栈
this.props.history.replace()
替代栈位置,即不会产生历史记录
this.props.history.goBack()
回退一格
this.props.history.goForward()
前进一格
this.props.history.go()
前进或者后退n格(根据传入的数字正负数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| 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) => {
this.props.history.replace(`/home/message/detail`, { id, title }) }
pushShow = (id, title) => {
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> */}
{/* 向路由组件传递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> <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> ) } }
|
withRouter的使用
- withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
- withRouter的返回值是一个新组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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)
|
BrowserRouter与HashRouter的区别
底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
但一般来说都用的这个
- HashRouter使用的是URL的哈希值。
path表现形式不一样
- BrowserRouter的路径中没有
#
,例如:localhost:3000/demo/test
- HashRouter的路径包含
#
,例如:localhost:3000/#/demo/test
刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为
state
保存在history
对象中。 - HashRouter
刷新后会导致路由state参数的丢失!!!
(不一定)
备注:HashRouter可以用于解决一些路径错误相关的问题。
每个 React Router 应用程序的核心都应该是一个路由组件。对于 web 项目,react-router-dom
提供<BrowserRouter>
和<HashRouter>
路由器。两者之间的主要区别在于它们存储 URL 以及与您的 Web 服务器通信的方式。
<BrowserRouter>
使用常规 URL 路径。这些通常是最好看的 URL,但它们需要正确配置您的服务器。具体来说,你的 Web 服务器需要在所有由 React Router 管理的客户端提供相同的页面。Create React App 在开发中开箱即用地支持这一点,并附带有关如何配置生产服务器的说明。<HashRouter>
将当前位置存储在URL的hash
一部分中,因此 URL 看起来像http://example.com/#/your/page
. 由于哈希永远不会发送到服务器,这意味着不需要特殊的服务器配置。
Hook
useHistory
该useHistory
挂钩使您可以访问history
可用于导航的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useHistory } from "react-router-dom";
function HomeButton() { let history = useHistory();
function handleClick() { history.push("/home"); }
return ( <button type="button" onClick={handleClick}> Go home </button> ); }
|
useLocation
该useLocation
钩子返回location
表示当前 URL 的对象。您可以将其视为每当 URL 更改时useState
返回一个新的。location
这可能非常有用,例如,当您希望在新页面加载时使用 Web 分析工具触发新的“页面查看”事件时,如下例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Switch, useLocation } from "react-router-dom";
function usePageViews() { let location = useLocation(); React.useEffect(() => { ga.send(["pageview", location.pathname]); }, [location]); }
function App() { usePageViews(); return <Switch>...</Switch>; }
ReactDOM.render( <Router> <App /> </Router>, node );
|
useParams
useParams
返回 URL 参数的键/值对的对象。用它来访问match.params
当前的<Route>
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Switch, Route, useParams } from "react-router-dom";
function BlogPost() { let { slug } = useParams(); return <div>Now showing post {slug}</div>; }
ReactDOM.render( <Router> <Switch> <Route exact path="/"> <HomePage /> </Route> <Route path="/blog/:slug"> <BlogPost /> </Route> </Switch> </Router>, node );
|