路由基本概念#
现代的前端应用大多都是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多个页面的功能,前端路由应运而生。
SPA的概念
1.单页Web应用(single page web application,SPA)。 2.整个应用只有一个完整的html页面。 3.点击页面中的链接不会刷新页面,只会做页面的局部更新。 4.数据都需要通过ajax请求获取, 并在前端异步展现。 单页面+多组件
路由概念
● 路由是一个比较广义和抽象的概念,路由的本质就是对应关系。
● 一个路由就是一个映射关系,一个key对应一个value
● key为路径, value可能是function或component
路由的分类:
后端路由:(根据路径向服务器端请求对应数据)
1)理解: value是function, 用来处理客户端提交的请求。
2)注册路由: router.get(path, function(req, res))
3)工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由:(根据路径展示对应的组件)
1)浏览器端路由,value是component,用于展示页面内容。
2)注册路由: <Route path="/test" component={Test}>
3)工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
react-router-dom
● react的一个插件库。
● 专门用来实现一个SPA应用。
● 基于react的项目基本都会用到此库。
下载方式:
install i react-router-dom
路由的基本使用
一、路由组件(Route,Link)
1. 使用步骤
1、安装: npm add react-router-dom 2、导入路由的三个核心组件:BrowserRouter/Route/Link 3、使用Router组件包裹整个应用 4、使用Link组件作为导航菜单(路由入口) 5、使用Route组件配置路由规则和要展示的组件(路由出口)
2. 路由组件说明
Router组件:包裹整个应用,一个React应用只需要使用一次 ● 两种常见Router:HashRouter、BrowserRouter ● HashRouter: 使用URL的哈希值实现(localhost:3000/#/first) ● 推荐使用BrowserRouter:使用H5的history api实现(localhost:3000/first) Link组件:用于指定导航链接(a标签) ● to属性:浏览器地址栏中的pathname(location.pathname) Route组件:指定路由展示组件相关信息 ● path属性:路由规则 ● component属性:展示的组件 ● Route组件写在哪,渲染出来的组件就展示在哪
3. 路由的执行过程
最后,我们再对React路由实现的原理做一个小结:
(1)使用Link等标签实现对浏览器path的操作(本质上是对BOM对象的history进行操作) (2)当前端路由器检测到浏览器的path发生了变化 (3)前端路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配 (4)当路由规则(path)能匹配地址栏中的pathname时,就展示该Route组件的内容
路由组件与一般组件的不同之处:
1.写法不同:
一般组件: <Demo/>
路由组件: <Route path="/demo" component={Demo}/>
2.存放位置不同:
一般组件: components
路由组件: pages
3.接收到的props不同:
一般组件: 写组件标签时传递了什么,props就能收到什么
路由组件: 接收带三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: “/home”
search: “”
state: undefined
match:
params: {}
path: “/home”
url: “/home”
路由组件与一般组件props接收的数据对比图:
二、React-route库的内置组件
1. NavLink组件
NavLink组件是在Link组件的基础上做了高亮特效的强,activeClassName=“active”
拓展:封装MyNavLink
NavLink总结:
NavLink与封装MyNavLink
1、NavLink可以实现路由链接的高亮,通过activeclassName指定样式名
2、标签体内容是一个特殊的标签属性
3、通过this.props.children可以获取标签体内容
2. Switch的使用(单一匹配)
当路由中出现了2个或者2个以上的path同时匹配的情况,那么实际上对应的路由组件都会被渲染。如果我们想要说只渲染(挂载)第一个匹配上的组件的话,那么我们可以使用 组件来解决。
未使用Switch组件的情况: /home对应着2个组件
引入Switch组件的情况
Switch总结:
Switch的使用
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
3. Redirect (路由重定向)
我们在进入网站的时候,网站的导航栏中往往会有一个默认选中的标签,对于这种场景,React路由其实也为我们提供了对应的组件来帮助我们简化开发,那就是Redirect组件。我们在快速入门的案例中新增加一个功能,进入首页后,默认跳转到/about路径下。
Redirect总结:
Redirect的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
三、解决样式丢失问题
样式丢失:
● BrowserRouter中存在的问题:相对路径导致,多级路径刷新页面样式丢失。
● 样式丢失的原因:混入了路径,一刷新,就找不到对应的样式css文件了
解决方法:
1、改变index.html中引入bootstap的路径
原本: <link rel="stylesheet" href="./css/bootstrap.css">
删去点:<link rel="stylesheet" href="/css/bootstrap.css">
原因:因为加了点的话,就是相对路径,就是从当前文件出发来查找,所以就从localhost:3000/gogocj文件路径出发来找就错了
如果不写点的话,就是直接在 localhost:3000/ 中找
2、修改引入link路径( %PUBLIC_URL% 这是适用于在React中写,指的就是public目录,相当于写死了)
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
这种就是绝对路径了,更加不可能有问题了,因为 %PUBLIC_URL% 表示的就是 localhost:3000
3、不改变link的路径,<link rel="stylesheet" href="./css/bootstrap.css"> 把BrowserRouter改成HashRouter
原因:hash路由中会有一个#,#号后面的东西都代表是前端的东西,是不会像后端请求的
然后一刷新的时候,都是直接忽略#号后面的东西了
也就是:我们在 localhost:3000/#/gogocj/home 刷新页面的时候,会把#省略,直接给localhost:3000/发送请求
总结:
解决多级路径刷新页面样式丢失的问题:
1.public/index.html 中 引入样式时不写 ./ 写 / (常用) 2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用) 3.使用HashRouter 总结:前两种都是改变link的路径,第三种是直接修改了路由的模式。
四、路由的模糊匹配和严格匹配
我们一般使用react路由时,链接要与组件相匹配,如下使用
<NavLink to='/home'>Home</NavLink>
<Route path='/home' component={Home}/>
1. 模糊匹配
如下所示,当链接第一级路径能匹配上相应的组件,而链接后面再多几级路径也不会产生影响,依旧能正常匹配(当然链接路径少于组件路径时不能匹配,像to=’/a/home/b’这样也无法匹配)
<NavLink to='/home/a/b'>Home</NavLink>
<Route path='/home' component={Home}/> //可以匹配
<Route path='/home/a/b/c' component={Home}/> //不可以匹配
<Route path='/a/home/b' component={Home}/> //不可以匹配
2. 严格匹配
有时我们不希望它进行模糊匹配,那么需要在Route上加一个属性exact,即严格的。这样模糊匹配将无法再正常匹配
<NavLink to='/home/a/b'>Home</NavLink>
<Route exact path='/home' component={Home}/>//不可以匹配
<Route exact path='/home/a/b' component={Home}/>//可以匹配 链接路径和组件路径,必须一模一样
总结:
路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致) 2.开启严格匹配:<Route exact={true} path="/about" component={About}/> 3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
五、嵌套路由
嵌套子路由
假如我们要给Home组件注册两个子组件路由的时候,
文件建立方式:
因为我们做的News和Message组件其实都是Home组件的子组件的,所以有两种文件建立的模式
(1)在Home文件夹下直接再建立文件夹
(2)在和Home同目录下,建立名为 Home_news的文件夹
路由的匹配
路由是有先后的注册顺序的,会先从最先注册的路由开始查询的
所以我们子组件嵌套路由,中的 to属性要做前面添加父组件的路径,比如:
<MyNavLink to="/home/news">News</MyNavLink>
//原因:由于模糊匹配的原则,前面的home会匹配到父亲组件,所以跳转到的路径就 localhost:3000/home/news 了
这里就是为什么如果有子嵌套路由的话,就不要开始严格匹配的原因
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
因为 /home/news 路由一开始是从这三个最先注册的路由开始匹配的,由于模糊匹配所以匹配上了Home,之后才到Home里面再进行子路由的匹配的
如果是严格匹配的话,那么就是直接<Redirect to="/about"/> ,直接就到了About组件了,所以就是从外层就屏蔽掉了,根本就进不去子路由了
总结:
嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
六、路由组件传参
常见的组件路由传递参数的方式有三种,分别是通过params参数传递、利用search属性传递、利用state属性传递
1. 传递params参数
为什么通过props可以获取到参数呢?我们不妨打印一下this.props看一下此时的Details 组件到底接收了哪些信息。
我们可以看到,此时组件的props属性中,有着三个属性,分别是history,location和match,而match`属性中已经帮我们把传递的参数封装成为一个对象了。
2. 传递search参数
● 这种方式基本上和我们常见的在url地址上进行参数拼接是一样的,
● 这种方式写法较为简单,只需要在 Link 标签中定义好key=value形式参数拼接,
● 然后在实际的组件中,直接从prop属性中获取,调用querystring(或者其他处理字符串转对象的函数库)处理参数,并封装成对象返回即可
注意:
● 这种方式下的传参并不需要额外对路由进行配置
● props.location.search属性中把我们Link的参数都封装了进去,但是这种方式封装的参数是字符串形式的,我们并不能直接使用,而是要借助queryString库来帮助我们把字符串解析成对象。
3. 传递state参数
● 和前两种传递参数的方式不同,这种方式传递的参数是不会在浏览器中显式的展示的,
● 同时,即使刷新页面,路由子页面的数据还是不会消失。
● 原因是react路由器帮我们对维护了history对象(history对象中又维护了location对象,所以也就有了state对象)
注意:
● 这种方式下的传参并不需要额外对路由进行配置
● location.state属性中,我们可以获取到我们在Link中传递的参数。
总结
向路由组件传递参数
1.params参数
● 路由链接(携带参数):<Link to=’/demo/test/tom/18’}>详情</Link>
● 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
● 接收参数:this.props.match.params
2.search参数
● 路由链接(携带参数):<Link to=‘/demo/test?name=tom&age=18’}>详情
● 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
● 接收参数:this.props.location.search
● 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
● 路由链接(携带参数):<Link to={{pathname:’/demo/test’,state:{name:‘tom’,age:18}}}>详情</Link>
● 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
● 接收参数:this.props.location.state
● 备注:刷新也可以保留住参数
七、多种路由跳转方式
1. 路由跳转的两种模式 push与replace
push: a-b-c 跳转会形成history,可返回到上一层
//push模式是栈的常规模式 跳转操作进入入栈
// 例: this.props.history.push('路由地址')
replace: a-b-c 跳转不会形成history,不可返回到上一层 适用于登录后,不需要重新回到登页面
//replace模式是替换模式,会替换掉栈顶的路由
//例: this.props.history.replace('路由地址')
2. 编程式路由导航
除了使用路由组件进行跳转之外,其实我们自己也可以利用事件处理函数来走路由跳转和参数传递的功能。
go & goBack & goForward
在路由组件中,我们可以通过this.prop.history获取到history对象,然后使用对象对应的API实现(历史)路径的跳转。简单理解的话,就是通过路由组件的history对象,实现页面前进、后退的功能。
总结:
编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
3.通过history对象主动调用push/replace方法
我们可以在路由组件中通过this.props.history获取到history对象后,通过主动调用push或者replace方法来进行路由的跳转,需要注意的是,当通过编程式事务主动进行路由跳转时,对应的参数需要和之前一样,根据传递方式的不同来定义:
传递参数格式根据传递方式的不同来定义:
八、withRouter的使用
高阶组件中的withRouter, 作用是将一个组件包裹进Route里面, 然后react-router的三个对象history, location, match就会被放进这个组件的props属性中.
// withRouter实现原理:
// 将组件包裹进 Route, 然后返回
// const withRouter = () => {
// return () => {
// return <Route component={Nav} />
// }
// }
// 这里是简化版
const withRouter = ( Component ) => () => <Route component={ Component }/>
//withRouter的返回值是一个新组件
如果我们某个东西不是一个Router, 但是我们要依靠它去进行浏览记录的前进后退 这时候就可以使用withRouter,将一般组件变成路由组件
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom' //引入withRouter
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)
//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可以用于解决一些路径错误相关的问题。
© 著作权归作者所有
发表评论