????????关注后回复 “进群” ,拉你进程序员交流群????????

作者丨前端发现者

来源丨前端发现

今日心血来潮,想起我们使用Vue开发单页面项目基本会用到 vue-router 路由插件,通过改变Url,在不刷新页面的情况下,更新页面视图。那么 vue-router 它是怎么实现路由跳转页面的呢?

好吧,没人理我就自己玩????。我(们)先来回忆下路由的配置:

router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)//声明路由表
const routes = [{name: 'login',path: '/login',component: () => import('@/views/login/index')},{name: 'register',path: '/register',component: () => import('@/views/register/index')}
]export default new Router({routes
})

main.js引入

import router from './router'new Vue({el: '#app',router,render: h => h(App)
})

App.vue使用路由组件

<template><div id="app"><router-view /></div>
</template>

目前vue-router提供路由跳转的方法有:

  • router.push          添加新路由

  • router.replace       替换当前路由

  • router.go            跳转到指定索引路由

  • router.back          返回上一个路由

  • router.forward       跳转下一个路由

以及常用的<view-link to="/login">去登录</view-link>

好了,vue-router路由的使用回忆完了,脑海是否存在一下问题?

  1. Vue.use(Router)时做了什么事情?

  2. <router-view />组件是怎么来的?

  3. <router-link />组件是怎么来的?

  4. router路由提供的编程式导航是怎么实现的?

  5. 浏览器Url地址发生变化时怎么渲染对应组件的?

我们知道,Vue中使用Vue-router的时候,实际是引入一个提供路由功能的插件,既然是插件,那么它就会向外提供一些方法供开发者使用。下面我们就针对上述的疑问一步步揭开谜底。

Vue.use(Router)时做了什么事情?

用户执行Vue.use的时候,其实是执行vue-router插件的 install 方法,并且把Vue的实例作为参数传递进去。

注:在Vue定义,只要插件中提供 install 方法就可以被Vue作为Vue.use(xx)来使用。翻看Vue-router源码的 src/install.js 文件,我们就可以看到下面这样的代码:

可以看到这个文件向外提供了 install 的方法。方法里面使用了Vue实例,并在实例中使用了 mixin 。那么在mixin中做了什么事呢?

  • 在 beforeCreate 生命周期中判断 this.$options.router 是否存在,这个东西就是我们在main.js文件中new Vue({})创建路由实例时传入的touter对象。

  • 在Vue实例中指定_routerRoot缓存下自身

  • 在Vue实例中指定_router缓存传入的router路由实例

  • 路由实例调用init方法,参数为Vue实例

  • 通过Vue的defineReactive方法将_route变成响应式,指向当前路由的URL。

  • 劫持数据_route,一旦_route数据发生变化后,通知router-view执行render方法

我们再来看看src/util/toute.js文件中创建路由的方法。

export function createRoute (record: ?RouteRecord,location: Location,redirectedFrom?: ?Location,router?: VueRouter
): Route {const stringifyQuery = router && router.options.stringifyQuerylet query: any = location.query || {}try {query = clone(query)} catch (e) {}const route: Route = {                             // 添加一个route对象name: location.name || (record && record.name), // 路由表配置的name属性meta: (record && record.meta) || {},           // 路由表配置的meta对象path: location.path || '/',                   // 路由表配置的path属性hash: location.hash || '',   query,params: location.params || {},fullPath: getFullPath(location, stringifyQuery),matched: record ? formatMatch(record) : []}if (redirectedFrom) {route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)}return Object.freeze(route)       // 最后将route对象冻结并返回(即不允许新增属性)
}

方法参数解析:

  • record:路由记录信息

  • location:需要跳转的路由地址(包含path、query、hash和params的对象)

  • router:router实例

<router-view />和<router-link />组件怎么来的?

你可能会注意到,我们在App.vue页面中会使用<router-view>和<router-link />组件,但是我们并没有手动引入和注册这两个组件,其实是vue-router内部帮我们去全局注册了组件。

还是刚才那个 install.js 文件

import View from './components/view'
import Link from './components/link'
...
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)

会看到这个几行代码。没错了,就是在执行install方法的时候就在Vue注册了组件了。

router路由提供的编程式导航是怎么实现的?

说到这里就要翻到src/index.js文件了。这里写了一个VueRouter类,VueRouter里面实现了编程式导航功能以及在constructor中看到了mode选项的配置。

从这也就知道了默认的路由渲染模式是hash,其中出现的options就是路由的配置。

接着往下走,来到第167行,会看到如下代码:

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.push(location, resolve, reject)})} else {this.history.push(location, onComplete, onAbort)}
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.replace(location, resolve, reject)})} else {this.history.replace(location, onComplete, onAbort)}
}
go (n: number) {this.history.go(n)
}
back () {this.go(-1)
}
forward () {this.go(1)
}

如此你的代码就可以这么写啦

router.push(location, onComplete?, onAbort?)
router.replace(location, onComplete?, onAbort?)
router.go(n)
router.back()
router.forward()

浏览器Url地址发生变化时怎么渲染对应组件的?

我们需要知道的是,当浏览器地址发生变化时:

HashHistory和HTML5History会分别监控hashchange和popstate来对路由变化作对应的处理。HashHistory和HTML5History捕获到变化后会对应执行push或replace方法,从而调用transitionTo来对路由变化作对应的处理。

上面提到在install方法的mixin中,会监听_route数据变化,一旦_route数据发生变化后,通知router-view执行render方法。这里就要回到刚才注册<router-view>组件那里去了。

翻到sec/components/view.js就能看到刚才注册的组件render函数啦

export default {name: 'RouterView',functional: true,props: {name: {type: String,default: 'default'}},render (_, { props, children, parent, data }) {data.routerView = trueconst h = parent.$createElement// 得到渲染的组件const name = props.name// route 对象const route = parent.$routeconst cache = parent._routerViewCache || (parent._routerViewCache = {})let depth = 0let inactive = falsewhile (parent && parent._routerRoot !== parent) {const vnodeData = parent.$vnode ? parent.$vnode.data : {}if (vnodeData.routerView) {depth++}if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {inactive = true}parent = parent.$parent}data.routerViewDepth = depthif (inactive) {// 非 keepalive 模式下 每次都需要设置钩子// 进而更新(赋值&销毁)匹配了的实例元素const cachedData = cache[name]const cachedComponent = cachedData && cachedData.componentif (cachedComponent) {if (cachedData.configProps) {fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)}return h(cachedComponent, data, children)} else {return h()}}const matched = route.matched[depth]const component = matched && matched.components[name]if (!matched || !component) {cache[name] = nullreturn h()}cache[name] = { component }data.registerRouteInstance = (vm, val) => {const current = matched.instances[name]if ((val && current !== vm) ||(!val && current === vm)) {matched.instances[name] = val}};(data.hook || (data.hook = {})).prepatch = (_, vnode) => {matched.instances[name] = vnode.componentInstance}data.hook.init = (vnode) => {if (vnode.data.keepAlive &&vnode.componentInstance &&vnode.componentInstance !== matched.instances[name]) {matched.instances[name] = vnode.componentInstance}handleRouteEntered(route)}const configProps = matched.props && matched.props[name]if (configProps) {extend(cache[name], {route,configProps})fillPropsinData(component, data, route, configProps)}return h(component, data, children)}
}

最后做个总结就是:

  • 向外暴露 install 和 router ,接着初始化路由。

  • 内部注册<router-view>和<router-link>组件。

  • 设置变量保存当前路由地址,监听hash变化,切换当前组件,然后render渲染对应的组件(hash模式)

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击????卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

震惊!Vue路由竟然是这样实现的!相关推荐

  1. umi权限路由_Umi 小白纪实(三)—— 震惊!路由竟然如此强大!

    在<Umi 小白纪实(一)>中有提到过简单的路由配置和使用,但这只是冰山一角 借用一句广告词,Umi 路由的能量,超乎你的想象 一.基本用法 Umi 的路由根结点是全局 layout  s ...

  2. 详解vue 路由跳转四种方式 (带参数)

    本文介绍了vue 路由跳转四种方式 (带参数),本文通过实例代码给大家介绍的详细,具有一定的参考借鉴价值,需要的朋友可以参考下 1. router-link 1. 不带参数 <router-li ...

  3. php vue jwt 实战,Vue路由之JWT身份认证的实现方法

    一.JWT身份认证简介 JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案,相较于session机制,服务器就不需要保存任何 session 数据了,也就是说,服务器变成无状态了 ...

  4. vue 路由传参 params 与 query两种方式的区别(转载)

    vue 路由传参 params 与 query两种方式的区别 初学vue的时候,不知道如何在方法中跳转界面并传参,百度过后,了解到两种方式,params 与 query.然后,错误就这么来了:  ro ...

  5. vue 一个页面多个router-view如何配置子路由_前端开发:如何安装配置Vue路由?

    大家好,我来了!本期为大家带来的Web前端学习知识是"前端开发:如何安装配置Vue路由?",喜欢Web前端的小伙伴,一起看看吧! Vue Router 是 Vue.js 官方的路由 ...

  6. vue路由 routers的写法:require用与不用

    vue路由的写法有很多种,这里我只说routers的写法,一种是compcomponent后面直接写路径,另一种是用require的方式,来看代码 import Vue from 'vue' impo ...

  7. vue路由-router

    VueRouter基础 vue路由的注册 导入 <script src="https://unpkg.com/vue-router/dist/vue-router.js"&g ...

  8. vue 传递 对象 路由_vue 04 -vue路由对象($route)参数简介以及和router的区别

    vue路由对象($route) 在使用了 vue-router 的应用中,路由对象会被注入每个组件中,赋值为 this.$route ,并且当路由切换时,路由对象会被更新. so , 路由对象暴露了以 ...

  9. vue 路由跳转并打开新页面

    let id ='123'; const {href} = this.$router.resolve( { path: '/home/test',query: {id: id}} ) window.o ...

最新文章

  1. 【青少年编程】【Scratch】04 事件模块
  2. JavaWeb:过滤器Filter
  3. 讲讲排序(C++描述 )
  4. mac在下面Apache 创 .htaccess档
  5. 重庆理工大学国际学院计算机图形学试题,哈尔滨理工大学-第一学期考试试题答案B卷考试.doc...
  6. 云服务器开启TCP Server 客户端无法连接的解决方法
  7. matlab图像信息熵交叉熵,【机器学习】信息量,信息熵,交叉熵,KL散度和互信息(信息增益)...
  8. Android开发之AudioManager(音频管理器)详解
  9. JPA用TABLE生成主键
  10. android alarmmanager管理,android alarmmanager需要权限吗
  11. 图书管理系统/库存管理系统等计算机毕业论文设计
  12. html+css+js实现简易计算器
  13. excel一列求和_学会这15组Excel函数,解决数据分析中80%的难题
  14. MiniDump - 调试问题的基础
  15. 天宫初级认证答案_百度初级认证考题_试题1题目+答案
  16. cups linux 升级_linux cups版本
  17. 网站外链如何才能被搜索引擎快速收录呢?
  18. 魔兽地图编辑器--人物自定义语音的方法和进入游戏自定义语音不能播放的问题
  19. 由浅入深,全面解析ThreadLocal
  20. 天下无贼是假的,天下无票倒是真的;如来神掌是假功夫,能买到车票才是真功夫。

热门文章

  1. 【网络安全面经】渗透面经、安服面经、红队面经、hw面经应有尽有 这一篇真的够了
  2. 团体程序设计天梯赛L3-014 周游世界
  3. Mint 9计算机桌面:皮实、单纯和阳光
  4. C++基础图论算法大集合 (图论,看我就行了)
  5. 【数据处理】箱线图(boxplot)的绘制
  6. 互联网开发搞手游创作5-策略性设计
  7. 一分钟了解英语表达:某方法是基于什么假设
  8. 51单片机初级项目开发10套资料(适合初学者练手题目)
  9. 学java可以从事什么工作-java岗位有哪些?
  10. Kotlin sortBy 、sortedBy、sortedWith、sortWith区别