前端入门之(vue-router全解析二)
Posted vv_小虫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端入门之(vue-router全解析二)相关的知识,希望对你有一定的参考价值。
前面一篇文章 前端入门之(vue-router全解析一)我们简单的了解了一下vue-router的原理,今天我们继续vue-router,我们从头到尾的全部撸一遍哈.小伙伴开车啦,跟紧了哦~~
我这里以vue-cli+webpack创建的但页面工程为例子了哈,我们直接选带有vue-router的模版代码为demo.
先上一下vue-router的官网:
https://router.vuejs.org/zh/guide/#javascript
在创建好的vue工程中,我们可以看到我们的main.js文件:
import router from './router'
....
new Vue(
el: '#app',
router,
store,
render(h)
return h(App)
)
然后是我们的router.js文件:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router(
mode:'hash',
routes: [
path: '/',
name: 'HelloWorld',
component: HelloWorld
]
)
然后在App.vue中配置router-view:
<template>
<div id="app">
<router-view/>
</div>
</template>
就这么简单的几个操作,我们的vue-router就可以跑起来了,好啦! 我们转过头来一步一步的研究vue-router是怎么走起来的.
首先我们看我们的router.js文件:
Vue.use(Router)
export default new Router(
...
)
我们看一下Vue.use(Router)干了什么~~
我们翻开vue-router的源码,找到install方法:
function install (Vue)
if (install.installed && _Vue === Vue) return
install.installed = true;
_Vue = Vue;
var isDef = function (v) return v !== undefined; ;
var registerInstance = function (vm, callVal)
var i = vm.$options._parentVnode;
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance))
i(vm, callVal);
;
Vue.mixin(
beforeCreate: function beforeCreate ()
if (isDef(this.$options.router))
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
else
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
registerInstance(this, this);
,
destroyed: function destroyed ()
registerInstance(this);
);
Object.defineProperty(Vue.prototype, '$router',
get: function get () return this._routerRoot._router
);
Object.defineProperty(Vue.prototype, '$route',
get: function get () return this._routerRoot._route
);
Vue.component('router-view', View);
Vue.component('router-link', Link);
var strats = Vue.config.optionMergeStrategies;
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
我们看到这么一段代码:
Vue.mixin(
beforeCreate: function beforeCreate ()
if (isDef(this.$options.router))
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
else
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
registerInstance(this, this);
,
destroyed: function destroyed ()
registerInstance(this);
);
在混入中添加了两个生命周期方法,一个beforeCreate、一个destroyed,在beforeCreate我们可以看到一行代码:
Vue.util.defineReactive(this, '_route', this._router.history.current);
可以说这一行代码发挥的作用还是挺大的,先剧透一下, 这个方法就是给_routerRoot也就是我们demo中的App.vue中的vue实例注册了一个动态变换属性“_route”, 在上一节中我们知道,vue-router原理就是使用pushState跟hash来做路由切换,vue-router会根据location的变换来动态的改变"_route"变量,而我们的"_route"变量中包含了当前路由需要渲染的组件等信息,而在router-view中,"_route"变量会当成属性传递给router-view,当_route变换的时候,我们的router-view就会重新走render方法,然后render方法中再根据_route中的当前组件,最后把当前页面渲染在页面.
好啦~!! 不知道小伙伴懂了没呢? 不懂也没关系,我们到时讲到vouter-view的时候还会走一遍代码,install方法最重要的也就是这一段代码了,我们继续往下走哈.
接着就是在Vue的原型中定义了两个属性 r o u t e r 跟 router跟 router跟route
Object.defineProperty(Vue.prototype, '$router',
get: function get () return this._routerRoot._router
);
Object.defineProperty(Vue.prototype, '$route',
get: function get () return this._routerRoot._route
);
所以我们可以在Vue的所有实例中使用this. r o u t e r 跟 t h i s . router跟this. router跟this.route了,this.$router就是我们传递的router对象:
export default new Router(
mode:'hash',
routes: [
path: '/',
name: 'HelloWorld',
component: HelloWorld
,
this.$route是当前的route对象,其中包含了当前页面中的所有信息.
接着就是注册了两个全局组件了:
Vue.component('router-view', View);
Vue.component('router-link', Link);
想必稍微了解过vue-router的小伙伴都知道这两个组件,router-view是router的根组件,所有的页面跳转都是基于router-view做跳转的.
router-link则是vue-router提供的一个方便开发者使用api而封装的一个组件,童鞋们也可以不用router-link,自己走api的方式来实现路由跳转.
好啦~ 分析了好久才把install方法简单地分析完,嘿嘿,真是佩服写框架的大佬们.
分析完install方法,我们紧接着来说一下vue-router组件,我们可以在vue-router的源码中看到vue-router组件的代码:
var View =
name: 'router-view',
functional: true,
props:
name:
type: String,
default: 'default'
,
render: function render (_, ref)
var props = ref.props;
var children = ref.children;
var parent = ref.parent;
var data = ref.data;
data.routerView = true;
// directly use parent context's createElement() function
// so that components rendered by router-view can resolve named slots
var h = parent.$createElement;
var name = props.name;
var route = parent.$route;
var cache = parent._routerViewCache || (parent._routerViewCache = );
// determine current view depth, also check to see if the tree
// has been toggled inactive but kept-alive.
var depth = 0;
var inactive = false;
while (parent && parent._routerRoot !== parent)
if (parent.$vnode && parent.$vnode.data.routerView)
depth++;
if (parent._inactive)
inactive = true;
parent = parent.$parent;
data.routerViewDepth = depth;
// render previous view if the tree is inactive and kept-alive
if (inactive)
return h(cache[name], data, children)
var matched = route.matched[depth];
// render empty node if no matched route
if (!matched)
cache[name] = null;
return h()
var component = cache[name] = matched.components[name];
// attach instance registration hook
// this will be called in the instance's injected lifecycle hooks
data.registerRouteInstance = function (vm, val)
// val could be undefined for unregistration
var current = matched.instances[name];
if (
(val && current !== vm) ||
(!val && current === vm)
)
matched.instances[name] = val;
// also register instance in prepatch hook
// in case the same component instance is reused across different routes
;(data.hook || (data.hook = )).prepatch = function (_, vnode)
matched.instances[name] = vnode.componentInstance;
;
// resolve props
var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);
if (propsToPass)
// clone to prevent mutation
propsToPass = data.props = extend(, propsToPass);
// pass non-declared props as attrs
var attrs = data.attrs = data.attrs || ;
for (var key in propsToPass)
if (!component.props || !(key in component.props))
attrs[key] = propsToPass[key];
delete propsToPass[key];
return h(component, data, children)
;
代码有点多,我们捡重点的来分析,通过源码我们可以看到,router-view是一个无状态的组件,也就是设置了functional属性,在vue的官网中我们可以找到,当一个组件设置了functional属性后,这个组件也就是一个无状态的组件(也就是没有data),无实例(没有 this 上下文),要深入研究的童鞋可以去看一下vue的官网
https://cn.vuejs.org/v2/guide/render-function.html
render: function render (_, ref)
...
var route = parent.$route;
...
var matched = route.matched[depth];
...
var component = cache[name] = matched.components[name];
...
return h(component, data, children)
;
在render方法中,我们可以看到,首先去获取route对象,然后获取route对象中的当前页面component,最后通过render的h方法把component(当前页面)渲染出来.
所以当我们执行vue-router的push、replace、back、go等方法的时候,其实就是改变location然后再改变当前route对象,当route对象改变的时候,就会触发router-view的render方法,然后render方法根据route做页面切换. 好吧,说了半天,想必小伙伴也都累了,我们就以vue-router的push方法为例子走一遍整个流程.
push这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
VueRouter.prototype.push = function push (location, onComplete, onAbort)
this.history.push(location, onComplete, onAbort);
;
因为vue-router中有hash跟history模式,我们就以html5History为例子了,我们找到HTML5History的push方法:
HTML5History.prototype.push = function push (location, onComplete, onAbort)
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route)
pushState(cleanPath(this$1.base + route.fullPath));
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
, onAbort);
;
幸好代码不是很多,我们可以看到最主要的就是执行了一个叫transitionTo的方法,我们点进去:
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort)
var this$1 = this;
var route = this.router.match(location, this.current);
this.confirmTransition(route, function ()
this$1.updateRoute(route);
onComplete && onComplete(route);
this$1.ensureURL();
// fire ready cbs once
if (!this$1.ready)
this$1.ready = true;
this$1.readyCbs.forEach(function (cb) cb(route); );
, function (err)
if (onAbort)
onAbort(err);
if (err && !this$1.ready)
this$1.ready = true;
this$1.readyErrorCbs.forEach(function (cb) cb(err); );
);
;
然后可以看到,又执行了一个叫confirmTransition的方法,简单说一下confirmTransition方法,confirmTransition方法主要是用于处理一些导航守卫函数, 一个一个的遍历这些守卫函数,最后执行,直到执行完所有的过渡函数,最后回调在transitionTo方法中传递的函数
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort)
...
this.confirmTransition(route, function ()
this$1.updateRoute(route);
...
, function (err)
...
);
;
可以看到,当执行完confirmTransition方法后,执行了一行代码:
this$1.updateRoute(route);
这行代码是干什么的呢? 我们继续往下走:
History.prototype.updateRoute = function updateRoute (route)
var prev = this.current;
this.current = route;
this.cb && this.cb(route);
this.router.afterHooks.forEach(function (hook)
hook && hook(route, prev);
);
;
可以看到,最主要的就是改变了current(也就是当前路由),然后执行了
this.cb && this.cb(route);
把当前路由传递给了cb方法,那么cb方法又是啥呢?找呀找呀,我们找到了HtmlHistory的父类:
History.prototype.listen = function listen (cb)
this.cb = cb;
;
所以我们需要看一下listen方法在哪调用的,我们继续找呀找呀,最后在vue-router的init方法中找到了它:
VueRouter.prototype.init = function init (app /* Vue component instance */)
....
history.listen(function (route)
this$1.apps.forEach(function (app)
app._route = route;
);
);
;
那么init方法又是哪调用的呢? 我们继续找呀找呀,最后在install方法中找到了init方法:
function install (Vue)
...
Vue.mixin(
beforeCreate: function beforeCreate ()
if (isDef(this.$options.router))
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
else
...
我重点看init方法中的这段代码:
VueRouter.prototype.init = function init (app /* Vue component instance */)
....
history.listen(function (route)
this$1.apps.forEach(function (app)
app._route = route;
);
);
;
遍历apps,然后给apps中的每个_route对象重新赋值最新的route路由信息,apps是什么呢? 从install方法中我们可以看到,apps其实就是像App.vue中的vue实例的一个集合, 因为我们demo中的rootRouter就一个,所以apps集合中也就一个vue实例,也就是App.vue.
文章一开始我们就解析了vue-router的install方法,在install中给rootRouter定义了一个可以变化的属性"_route":
Vue.util.defineReactive(this, '_route', this._router.history.current);
所以当_route改变的时候,我们的的router-view重新走render方法,页面改变.
我们回到HTML5History的push方法:
HTML5History.prototype.push = function push (location, onComplete, onAbort)
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route)
pushState(cleanPath(this$1.base + route.fullPath));
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
, onAbort);
;
前面分析我们直到,当我们执行完transitionTo方法的时候,其实我们的页面已经改变了,但是我们的链接地址还没变,所以接下来就调用了 pushState方法,在上一节中,我们已经认识了pushState方法了,所以我们点进去看看是不是跟我们上一节说的一样:
function pushState (url, replace)
saveScrollPosition();
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
var history = window.history;
try
if (replace)
history.replaceState( key: _key , '', url);
else
_key = genKey();
history.pushState( key: _key , '', url);
catch (e)
window.location[replace ? 'replace' : 'assign'](url);
哈哈!! 我就不解释了哈,小伙伴感兴趣的可以去看我上一篇文章
https://blog.csdn.net/vv_bug/article/details/82795248
好啦,到这里vue-router的全部流程我们就简单的走完了,小伙伴要深入研究的自己可以去试着看看源码,也可以一起交流学习一下哈,嘿嘿!!! 最后还落了一个router-link组件, 好晚啦,实在写不动了,放到下一节了~
欢迎入群,欢迎交流,qq群链接:
以上是关于前端入门之(vue-router全解析二)的主要内容,如果未能解决你的问题,请参考以下文章