思维导图
开始
先看平时使用的 Vue-Router
,引入Router
, Vue.use
注册插件。直接从这里开始入手
使用场景
import Vue from 'vue'
import Router from "../vue-router"
import routes from './routes'
Vue.use(Router)
let router = new Router({
routes
})
index
先看vue-router.js
文件,先生成一个VueRouter
类,然后导入install
方法,因为Vue-Router
的install
方法比Vuex
复杂一些,所以将install
单独作为一个文件。
import install from './install';
class VueRouter {
constructor(options) {
}
}
VueRouter.install = install;
export default VueRouter
Vue.use()
来先看 Vue.use()
的源码中的一部分,这里面判断注册的插件里的install
是不是一个函数,有就执行插件里的install
函数。或者判断插件本身是不是一个函数,有就执行插件本身。这里本质上是没有区别,有没有install
都可以。而VueRouter
使用了install
,目的是为了将install
作为入口函数,方便封装,同时也将install
和其他代码分开。
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
install
上述已经将vue-router
的类构建好,现在VueRouter
实例已经有了,然后执行vue.use()
,然后会执行VueRouter
类里的install
函数,那来看install.js
。
install里使用了Vue.mixin
,混入代码,下面代码是在当前组件生命周期beforeCreate
里混入代码,代码逻辑是判断当前组件是否为根组件,如果是则将_routerRoot
作为键放入当前组件中,值为Vue
实例。再将_router
作为键放入当前组件中,值为VueRouter
实例。然后执行初始化操作。如果不为当前组件不是根组件,则该组件为根组件的子组件。将_routerRoot
作为键放入当前组件中,值为父组件的_routerRoot
,从父亲身上获取.
$route
和$router
是利用Object.definePropert
代理_routerRoot
里的_router
和_route
,访问到的。
接着注册全局组件router-view
和router-link
import RouterView from '../components/router-view'
import RouterLink from '../components/router-link'
const install = (_Vue, s) => {
_Vue.mixin({
beforeCreate() {
if (this.$options.router) { // 判断是不是根组件
this._routerRoot = this; // 把vue实例放在当前组件的——routerRoot属性上
this._router = this.$options.router // 这里直接获取根实例
this._router.init(this); // 初始化
_Vue.util.defineReactive(this, '_route', this._router.history.current) // 将属性_route成为响应式属性
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot // 这里的是从父亲最顶层获取Router实例
}
}
})
Object.defineProperty(_Vue.prototype, '$route', { // 代理$route
get() {
return this._routerRoot._route
}
})
Object.defineProperty(_Vue.prototype, '$router', { // 代理$router
get() {
return this._routerRoot._router
}
})
_Vue.component('router-view', RouterView) // 注册组件router-view
_Vue.component('router-link', RouterLink) // 注册组件router-view
}
export default install
init
然后回到VueRouter
类中,此时多了个init
函数。目前做的路由方式是hash
方式,还有另外俩种方式,history
,abstract
。因为有三种方式,所以Vue-Router
做了一个父类base
执行同样的逻辑,子类三种方式继承父类base
,再独自执行自己方式的代码。
通过new HashHistory
获取 history
实例,初始化init
执行history
实例对应函数。将目光放到history
实例上,这些函数来自于base.js
和hash.js
。
import install from './install';
class VueRouter {
constructor(options) {
this.history = new HashHistory(this);
}
init(app) {
const history = this.history;
const setupHashLister = () => {
history.setupLister(); // hash的跳转
}
history.transitionTo(
history.getCurrentLocation(),
setupHashLister
)
history.listen((route) => {
app._route = route
})
}
}
VueRouter.install = install;
export default VueRouter
base.js
先看构造函数construcror
,将router
作为键放在自身实例上,值为VueRouter
实例,curent
为当前导航正要离开的路由,也就是路由守卫的参数里的from
1.transitionTo()
为跳转后立即执行的函数,传入当前路径和回调函数,r
为$route
,是扁平化后的配置,也就是即将要进入的目标 路由对象
2.cb
是History
的listen()
函数,将$route
放入当前组件上供用户使用。
3.callback
是执行HashHistory
的setupHashLister()
函数,是给当前window
添加监听事件onhashChange
,onhashChange
后续通过hash
变化执行transitionTo
进行更新。
4.最后将r
赋值给current
,更新路由信息。
class History {
constructor(router) {
this.router = router;
this.current = createRoute(null, {
path: '/'
})
}
transitionTo(location, callback) {
let r = this.router.match(location)
if (location == this.current.path && r.matched.length == this.current.matched.length) { // 防止重复跳转
return
}
this.cb && this.cb(r);
callback && callback();
this.current = r;
}
listen(cb) {
this.cb = cb;
}
}
hash.js
hash
方式的函数就简单介绍一下,看构造函数constructor
,跟父类一样赋值router
。执行ensureSlash
函数,因为hash
相比其他函数,一进入页面就会多个#。所以就初始化的时候处理一下。getCurrentLocation
函数是获取当前路径的,push
是hash
方式的跳转,setupLister
函数是刚刚所述的监听函数hashchange
。
import Histroy from './base';
function ensureSlash() {
if (window.location.hash) {
return
}
window.location.hash = '/';
}
class HashHistory extends Histroy {
constructor(router) {
super();
this.router = router;
ensureSlash();
}
getCurrentLocation() {
return window.location.hash.slice(1);
}
push(location){
this.transitionTo(location,()=>{
window.location.hash = location
})
}
setupLister() {
window.addEventListener('hashchange', () => {
this.transitionTo(window.location.hash.slice(1));
})
}
}
export default HashHistory
扁平化
刚刚base.js
里执行的this.router.match(location)
以及createRoute()
,都是需要建立在扁平化配置基础之上的。
平时配置的路由是这样的,需要将配置进行扁平化,才能用得上。
[
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About,
children: [
{
path: 'add',
name: 'add',
component: Add
},
{
path: 'bull',
name: 'bull',
component: Bull
}
]
}
]
扁平化后是这样的
/: {path: "/", component: {…}, parnent: undefined}
/about: {path: "about", component: {…}, parnent: {…}}
/about/add: {path: "add", component: {…}, parnent: {…}}
/about/bull: {path: "bull", component: {…}, parnent: {…}}
接着看扁平化函数createMatcher以及createRouteMap
createMatcher
createMatcher
返回一个match
函数,match
方法是匹配路径,根据路径拿扁平化对象里的配置,然后执行createRoute
方法,将其转化为route
,返回。pathMap
由createRouteMap
生成
import createRouteMap from './create-route-map'
import { createRoute } from './history/base';
export default function createMatcher(routes) {
let { pathList, pathMap } = createRouteMap(routes);
function match(location) {
console.log(pathMap)
let record = pathMap[location];
return createRoute(record,{
path: location
})
}
return {
match
}
}
createRouteMap
将routes
配置传入createRouteMap
中,遍历routes
,进行扁平化操作
pathMap
以路径为键名,值为一个对象包裹着路径,组件,父组件。
将路径匹配上父组件的路径和自身的路径
如果有子组件就进行递归,全部转为扁平化返回。
export default function createRouteMap(routes, oldPathList, oldpathMap) {
let pathList = oldPathList || [];
let pathMap = oldpathMap || Object.create(null);
routes.forEach(route => {
addRouteRecord(route, pathList, pathMap);
})
return {
pathList,
pathMap
}
}
function addRouteRecord(route, pathList, pathMap,parnent) {
let path = parnent ? `${parnent.path}/${route.path}`: route.path;
let record = {
path: route.path,
component: route.component,
parnent
}
if (!pathMap[path]) {
pathList.push(path);
pathMap[path] = record;
}
if(route.children){
route.children.forEach(route=>{
addRouteRecord(route, pathList, pathMap,record)
})
}
}
createRoute
createRoute
是生成$route
的函数,传入参数为扁平化配置,路径。将res
作为空数组,如果传进来的扁平化配置有值,则进行while
循环,将自己从数组头部插入,取出父组件再从头部插入,如此反复,得到一个含着层次关系的数组。将loaction
和数组包裹为对象返回。
export function createRoute(record, location) {
let res = [];
if (record) {
while (record) {
res.unshift(record)
record = record.parnent
}
}
return {
...location,
matched: res
}
}
router-view
然后在来看看routerview
是一个函数式组件,
返回render
方法
进行while
循环,遍历出嵌套的routerview
用depth
作为深度,也是matched
的index
.
每遍历一次,就在$vnode.data.rouView
改为true
,将深度加1
返回对应的组件即可
export default {
name:'routerView',
functional: true,
render(h,{parent,data}) {
let route = parent.$route
let depth = 0;
while (parent) {
if (parent.$vnode && parent.$vnode.data.routeView) {
depth++;
}
parent = parent.$parent;
}
data.routeView = true;
let record = route.matched[depth];
if (!record) {
return h();
}
return h(record.component, data);
}
}
router-link
再来看看routerlink
没什么东西就返回一个a
标签,用插槽把对应的文本显示出来,在添加的跳转事件
调用$router
的push
方法,也就是Router
类上的push
export default {
name: 'routerLink',
props: {
to: {
type: String,
required: true
},
tag: {
type: String,
default: 'a'
}
},
methods: {
handler(to) {
this.$router.push(to) // 路由跳转
}
},
render() {
return <a onClick={this.handler.bind(this,this.to)}>{this.$slots.default[0].text}</a>
}
}
Vue-Router草稿 :https://shimo.im/docs/TqKywdwhwV9Jrqg8
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!