前端路由(二)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端路由(二)相关的知识,希望对你有一定的参考价值。

参考技术A 前面我们做到了切换路由不发送请求,现在我们要 把路由和组件对应起来达到渲 染。

        接下来介绍的是VueRouter的原理,它是怎么做到的这个功能的。其会在 根Vue 上注册2个全局 函数式组件 <router-link> <router-view>,在根Vue原型上 定义$route(当前路由Route对象)和$router(传入newVue的router对象列表)两个属性(所有子Vue实例会继承)。 

        <router-link>,作为一个子组件,初始化渲染时会去执行render函数,主要做了 其 内部属性 (tab标签,activeClass等)的 处理 ,在点击时会去 执行router.push做url变化 。它不涉及渲染,逻辑比较简单。

        执行router.push(replace)做url变化和初始化VueRouter时都会去触发 transitionTo方法做路径切换 。这个方法里做了很多事情,接下来会介绍。它执行完毕的成功回调中会切换url。

        最重要的问题是渲染组件 , <router-view>如何知道去渲染哪个组件 ?我们手头有一个按文档规定写的new VueRouter( routers )列表,传给了根Vue,VueRouter通过它做了很多事情。

        首先在new VueRouter()时,会去执行其构造函数,其中 createMatcher 方法,会递归遍历routers把每个router对象进行重新描述得到 RouteRecord 对象,并由它们得到3个列表( pathList (路由path列表), pathMap (路由path: record列表), nameMap (路由name:record列表)),这3个列表是为导航守卫服务的我们先不管它。createMatcher最后返回2个方法,1、 addRoutes ,动态对上面3个列表修改。2、 match ,根据传入的位置和当前的路径,计算出新的路径为 Route对象。

        transitionTo路径切换时,会去执行 match 函数计算新路径Route对象,其有一个属性值 matched ,是从当前RouteRecord向上(parent)查找直到根RouteRecord的到的RouteRecord数组,这样得到一个 层级关系 。我们在<router-view>的render函数中会标志flag表示是router-view组件,我们上(父组件)查找有flag标志就 会把 depth++,最后得到 当前<router-view>的深度 。我们通过 $route.matched [depth]就可以 找到router-view需要去渲染的组件。

        这里还有一个问题, 我们怎么知道当前$route是哪个? 在初始化routerVue,init()中规定了所有子组件的实例的$route属性指向根Vue的$route属性,根Vue的$route属性又等于this._router.history.current。而current这个值又是在路径切换时会变化的。也就是说我们路径变化会把当前$route指为当前组件路由的route路径对象,这个我们不用担心。

         概括: 就是做路径切换时,我们会根据我们写的路由表,把当前路由往上到根路由的路由对象组成一个数组,描述一个 层级关系List 。<router-view>是函数式组件,它有标志flag,我们也会从它往上查找<router-view>直到根vue,有就depth++,得到当前<router-view> 层级位置depth 。 List[depth].component就是我们要渲染的组件。

         我们知道<router-view>要去渲染哪个组件,它是怎么做到更新视图的? 更新视图肯定也要符合Vue渲染原理呀,要把1个数据响应式化,get时收集订阅者Watcher并初始化渲染,set时派发更新 把订阅该数据的Watcher重新渲染。这个响应式数据是谁呢?就是定义在根Vue的_route,子Vue的$route也都指向它。<router-view>函数render执行的时候,会去取$route相当于访问根Vue的_route,会触发订阅者Watcher收集并初始化渲染。做路径切换时,会修改_route(即记录当前路由路径的this._router.history.current)。

触发点 :router-link 提供了"下一个位置参数",准备去切换url时,触发transitionTo方法去做路径切换。

响应式化数据 :history.current。记录当前路径Route对象(由当前路由和下一个位置计算出来),收集订阅者,派发更新渲染,都围绕它进行。

路由配对组件 :当前路径Route对象(由当前路由和下一个位置 计算出来)和我们写的路由表,得到当前路径Route对象到根路径的 路由路线 数组,<router-view>肯定在这之内,不会比其更深层,所以在这数组之内可以用depth取到。

         守卫导航是怎么做到的 ?守卫导航就是transitionTo方法做路径切换时执行的一系列钩子函数。

        这些钩子函数 有些定义在全局 ,用 this.router.xxx可以取到。

         有些定义在组件内 ,通过前面的Routed.matched,即将离开路由的matched列表和当前路由的matched列表,从头对比到第一个不同,得到 updated (目标RouteRecord和当前RouteRecord相同,前面重复的部分)、 activated( 目标RouteRecord和当前RouteRecord不同,后面不同的部分 ) 、 deactivated( 当前RouteRecord和目标RouteRecord不同,后面不同的部分 ) 三个RouteRecord数组。 通过这些RouteRecord去取定义在组件中的导航守卫 。

         有些定义在路由中 ,也通过这些 RouteRecord去取。

到这里就完成了,接下来会分析前端路由在实际开发中的运用,比如页面权限设置!

React路由

React路由:

一、前端路由的工作原理:

其实是利用前端中的BOM对象中的history这个属性来进行的实现。

二、实现前端路由的两种方法:

方法一:直接使用H5推出的history身上API。但是有些旧的浏览器可能不支持。这里使用的BrowserRouter

方法二:hash值(锚点)。兼容性好,会在url地址中出现#字符。使用的是HashRouter。URL的哈希值,#后面的内容不会发送给服务器,但是浏览记录会保存在历史记录中。

BrowserRouter与HashRouter的区别:

除了上面提到的不同之外,刷新后对路由state参数的影响也是不同的:

1.BrowserRouter没有任何影响,因为state保存在history对象中。

2.HashRouter刷新后会导致路由state参数的丢失。

备注:HashRouter可以用于解决一些路径错的相关的问题。

三、react-router-dom:

有三个版本:web,native,anywhere

yarn addd react-router-dom

1.react的插件库

2.专门用来实现一个SPA应用

3.基于React的项目基本都会使用到此库。

四、项目中路由的使用

//在react中router有两种,所以在应用的时候需要声明到底是哪一种router:BrowserRouter,HashRouter
import Link,BrowserRoueter,Route from 'react-router-dom'
import Home form './pages/Home'
import About from './pages/About'

//在return的虚拟dom中
    	<Link className="list-group-item" to="/about">About</Link>
		<link className="list-group-item" to="/home">Home</link>
//显示区 注册路由
<div className="panle-body">
        <Route path="/about" component=About/>
        <Route path="/home" component=Home/>
</div>
    
    
 //为了实现路由的跳转,还要进行BrowerRouter属性的设置。在App的外部包裹一个BroswerRouter
   设置的方法一:在最外层的index.js中引入BrowserRouter这个属性,表示App组件中的所有的路由跳转都包含在该属性中。

index.js
//引入BrowserRouter
import BrowserRouter from 'react-router-dom'
//引入App
import App from './App'
ReactDOM.render(
	<BrowserRouter>
    	<App/>
    </BrowserRouter>
),document.getElementById("root")

上面的Home和About组件都是通过路由的方式渲染的,所以称为路由组件。一般来说,路由组件不放在coponents文件夹下面,而是放在pages文件夹下面。

一般组件和路由组件最大的区别就是在props上面。以班组件需要手动添加props内容才能收到props,但是路由组件,即使没有传递props也可以接收到props。其中有三个属性,分别是history,location,match。

history:  //进行路由跳转时使用
	go:   
	goBack:  //返回上一级
	goForward://到达下一级路由
	push://跳转到指定路由
	replace://跳转到指定路由,不留历史记录
location:
	pathname:'/about', //路由路径
	search:""  //使用search方式传递参数时,数据保存在这里
	state:undefined   //路由传递参数
match:
	isExact:true,
	params:,
	path:"/aboute",
	url:"/about"

五、现在要实现点击nav之后点击的哪个元素实现高亮效果。

1.将标签Link换成标签NavLink。在使用之前首先要将该属性给引入。import NavLink from 'react-router-dom'

<NavLink activeClassName="hello"  className="list-group-item" to="/about">About</NavLink  > //点谁就给谁添加高亮的类名

优化:

从上面的写法上看,发现当有多个路由组件的时候,每次都要写其中的高亮类名或者其他公用的属性,这里看起来就不是很好,我们解决的方法就是自己封装一个组件。

1.封装NavLink组件叫做myNavLink

import NavLink from 'react-router-dom'
export default class MyNavLink extends Components
    const to,title =this.props
	render()
		return(
			<NavLink activeClassName="hello" className="list-group-item" to=to>title</NavLink> //写法1
            <NavLink activeClassName="hello" className="list-group-item" ...this.porps</NavLink>//写法2:直接将传递过来的参数使用展开的方式添加入属性。
		) 
	

//在界面引入该组件
import myNavLink from './components/myNavLink'
<MyNavLink to="/about" title="About"></MyNavLink>
<MyNavLink to="/home" title="Home"></MyNavLink>

2.上面是通过将属性放在参数中然后利用props的方式获取值的方式。但是还有更加符合规范的写法:

import NavLink from 'react-router-dom'
export default class MyNavLink extends Components
	render()
		return(
			<NavLink activeClassName="hello" className="list-group-item" ...this.props ></NavLink> //这里不需要在组件之间写About或者是Home,因为在组件的props中有一个chidren属性,该属性的值就是组件之间的值。
            //也就是下面组件传递的About在内部中通过this.props.children能够获取到。且我们通过在组件中设置属性children也可以添加组件之间的值。
          
		) 
	

//在界面引入该组件
import myNavLink from './components/myNavLink'
<MyNavLink to="/about">About </MyNavLink>  //不通过title属性传递了
<MyNavLink to="/home"> Home </MyNavLink>

六、注册路由时注意的问题:

当注册的路由有多个时候,常在注册路由的包裹一个父级标签,switch

<Switch>
	<Route path="/about" component=About/>
    <Route path="/home" component=Home/>
    <Route path="/home" component=Home/>
</Switch>
//当路由进行匹配的时候,正常情况下都会从上面往下扫描,一旦发现相同的路径则匹配成功。跳转到相应的页面中。加入Switch标签后,一旦发现相同的路径则不会继续往下匹配。

七、解决多级路径刷新页面样式丢失的问题

在路由中的路径写成多级的形式的时候,每次刷新页面都出现页面样式丢失的问题。

解决方法:

1.将index.html页面中的样式引入相对路径改为绝对路径

2.在路径前面加一个%PUBLIC_URL%/

3.直接改用HashRoute这种模式,这样就不会将主机名和端口号后面的内容传递给服务器。

八、路由的模糊匹配和精准匹配

如果是模糊匹配,比如在匹配路径的时候,从左往右匹配,只要遇到相同的路径名称,就可以成功匹配。

如果是精准匹配,需要给路由加一个属性 exact=true在匹配的时候只有路径完全相同才能进行匹配,否则无法匹配。

<Route exact  path='/about'/> //精准匹配

严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由。

九、重定向Redirect

import Redirect from 'react-router-dom'
<Route path="/about" component=About/>
<Route path='/home' component=Home/>
<Redirect/>  //重定向标签,当无法匹配到上面的路径时,默认情况下是该路径下面的组件进行匹配。

10、嵌套路由

  • 注册子路由时要写上父路由的path值
  • 路由的匹配是按照注册路由的顺序进行的

11.路由参数

1.由路由组件传递params参数

//在路由跳转的时候携带参数到指定的页面 向路由组件传递params参数
<Link to=`/home/message/detail/$msgob.id/$msgobj.title`></Link>

//注册路由时声明接收params参数
<Route path="/home/message/detail/:id/:title" component=Detail></Route>
//接收参数:在detail组件中获取传递过来的参数
export default class Detail extends Component
    render()
        //所有的params参数都放在了this.props.match.params对象中
        const id,title=this.props.match.params
		return(
        		<ul>
            		<li>ID:id</li>
                	<li>titile:title</li>
            	</ul>
        	)
        

2.向路由组件传递search参数

//在路由跳转的时候携带参数到指定的页面,参数的格式的 ?+参数 和ajax请求的参数格式类似,这里叫做search参数
<Link to=`/home/message/detail/?id=$msgObj&title=$msgObj.title`></Link>

//search参数无需声明接收
<Route path='/home/message/detail' component=Detail></Route>

//接收参数
export default class Detail extends Component
    //接收search参数 参数存放在this.props.location.search这个属性中
    console.log(this.props.location.search);//得到的结果是'?id=1&titile=hello'的格式
	const search =this.props.location;
	const id,title = qs.parse(search.slice(1))
    render()
        return(
        		<ul>
            		<li>ID:id</li>
                	<li>titile:title</li>
            	</ul>
        	)
        
    

补充:像上面的 key=value&key=value这种格式的字符串叫做urlencoded格式,有一个专门处理这种数据格式库qs

import qs from 'querystring'
let obj=name:"tom",age:18 
qs.stringfy(obj);	//将json格式的数据转换为urlencoded编码格式
let str='carName=奔驰&price=199'
qs.parse(str);//将urlencoded编码格式的数据转换为json格式  carName:'奔驰',price=199

3.向路由传递state参数

state参数的特点就是参数不存在路径当中

备注:刷新也可以保留住参数

这里的state跟组件中的state不是一个东西。

//向路由组件传递state参数
<Link to=parthname:'/home/message/detail',state:id:msgObj.id,title:title></Link>
<Route path='/home/message/detail' component=Detail></Route>

//接收state参数
export default class Detail extends Component
    //接收search参数 参数存放在this.props.location这个属性中
	const id,title =this.props.location.state;
    render()
        return(
        		<ul>
            		<li>ID:id</li>
                	<li>titile:title</li>
            	</ul>
        	)
        
    


12、路由跳转的两种模式pushreplace

路由的push操作是将每次点击的路径加入到栈中保存,最新的路由路径在最上面。在默认情况下,路由的跳转是push类型。只有添加特定的属性从而转换成replace的类型

<Link replace to=pathname:'/home/message/detail',state:id:msgObj.id,title:msgObj.title></Link>

这样就不会在栈中保留历史痕迹。

13、编程式导航

上面的案例都是声明式导航,编程式导航和声明式导航不同的点在于,声明式导航需要有人点击才能发生跳转,业务需求有时候需要自动跳转,或者是其他的情况,这时候就需要用到了编程式导航。

<button>push查看</button>
<button onClick=this.replaceShow>replace查看</button>

replaceShow=(id,title)=>
	//编写一段代码,让其实现跳转到Deatil组件,且为replace跳转携带params参数
    //上面我们知道路由组件中的props有三个属性,利用这些属性实现跳转
    this.props.history.replace(`home/message/detail/$id/$title`)
    //同样的跳转方式,携带search参数
    this.props.history.replace(`home/messege/detail?id=$id&title=&title`)
    //同样的跳转方式。携带state参数
    this.props.history.replace('home/message/detail',id,titile)

//使用push跳转,所有携带参数跳转的写法和上面的replace写法类似
pushShow=(id,titile)=>
	this.props.history.push('home/message/detail',id,title)

//回退和前进
back=()=>
    this.props.history.goBack()

forward=()=>
    this.props.history.goForward()

GO=()=>
    //正负号表示前进后退,数值大小表示距离
	this.props.history.go(1);//前进一步
    this.props.history.go(-1);//后退一步
    

<Button onClick="back">后退</Button>
<Button onClick="forward">前进</Button>
<Button onClick="go">GO</Button>

14、withRouter

withRouter可以加工一般组件,它是一个函数,让一般组件具备路由组件所特有的API,它的返回值一个新的组件。

import withRouter from 'react-router-dom'
class Header extends Component


export default withRouter(Header)

以上是关于前端路由(二)的主要内容,如果未能解决你的问题,请参考以下文章

浅谈前端路由原理hash和history

React路由

React路由

React路由

前端进阶全栈入门级教程nodeJs博客开发(一)搭建环境与路由

前端进阶全栈入门级教程nodeJs博客开发(一)搭建环境与路由