【仿vue-router源码实现】

本文介绍了如何从基础开始实现一个简单的vue-router,包括创建RouterTable、RouterView、RouterLink以及导航守卫的实现。通过理解H5Mode的工作原理,利用popstate监听路由变化,并通过vue实例的_route属性来实现组件的动态渲染。同时,详细讲解了RouterLink的创建和导航守卫的注册及执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

vue-router用于处理单页面路由分为H5mode和Hashmode,今天我们就来实现一下


一、基础实现

实现我们需要建一个RouterTable类,来进行路由和组件的关联关系
在Router里我们需要两个值,自己的history和RouterTable
router.js:

import Vue from 'vue'
import H5Mode from './history/h5'
import RouterView from './component/RouterView.vue'
import RouterLink from './component/RouterLink.vue'
Vue.component("RouterView", RouterView)
Vue.component("RouterLink", RouterLink)
class RouterTable { //路由和组件对应的关联关系
    constructor(routes) {
        this._pathMap = new Map()
        this.init(routes)
    }

    init(routes) {
        const addRouter = route => {
            console.log(route);
            this._pathMap.set(route.path, route)
        }
        routes.forEach(route => {
            addRouter(route)
        });
    }

    match(path) {
        let find;
        console.log(this._pathMap);
        console.log(this._pathMap.keys());

        for (const value of this._pathMap.keys()) {
            if (path === value) {
                find = value
                break
            }
        }
        return this._pathMap.get(find)
    }
}
export default class Router {
    constructor({ routes = [] }) {
        this.routerTable = new RouterTable(routes)
        this.history = new H5Mode(this)
    }

    init(app) {
        console.log(app);
        console.log(this);
        const { history } = this
        history.listen(route => {
            app._route = route
        })
        history.transitionTo(history.getCurrentLocation()) //app._route = route path: '/home', component
    }

    push(to) {
        this.history.push(to)
    }
}
Router.install = () => {
    Vue.mixin({
        beforeCreate() {
            if (this.$options.router !== undefined) {
                this._routerRoot = this
                this._router = this.$options.router
                this._router.init(this)
                Vue.util.defineReactive(this, "_route", this._router.history.current) //{path: '/home', component: {…}}
            }

        },
    })
}

其中Map是一组键值对的结构,具有极快的查找速度。就相当于一个二维数组。
for-in是ES5标准,遍历的是key(可遍历对象、数组或字符串的key);for-of是ES6标准,遍历的是value(可遍历对象、数组或字符串的value)。
创建监听路由变化的类
因为有H5和Hash两种模式,所以我们先创建一个基本的类
base.js:


export default class HistoryBase {
    constructor({ routerTable }) {
        this.routerTable = routerTable
    }

    listen(cb) {
        this.cb = cb
    }

    transitionTo(target) { //pathname:/home

        const route = this.routerTable.match(target)
        this.current = route
        this.cb(this.current)
        // route => {
        //     app._route = route
        // }(this.current) 
    }
}

我们主要采用H5Mode所以创建
h5.js:

import HistoryBase from './base'
export default class H5Mode extends HistoryBase {
    constructor(options) {
        super(options)
        this.initListener()
    }

    initListener() {
        window.addEventListener("popstate", () => {
            this.transitionTo(this.getCurrentLocation())
        })
    }

    getCurrentLocation() {
        const path = window.location.pathname
        return path
    }

    push(target) {
        this.transitionTo(target)
        window.history.pushState({ key: +new Date() }, "", target)
    }
}

它主要是通过popstate来进行监听的,如果路由变了就通过app._route = route改变,这个时候RouterView.vue又会监听app._route 来返回不同的组件

二、创建RouterView

它的作用是将组件渲染出来,他需要通过this.$root._routerRoot._route拿到component,因为当时我们存的是Vue.util.defineReactive(this, “_route”, this._router.history.current),其中第三个参数就是一个{path: ‘/home’, component: {…}}

<script>
export default {
    mounted(){
console.log(this);
    },
    render(){
       const route=this.$root._routerRoot._route;
       if(!route){
           return
       }
       const { component }=route
       return <component />
    }
}
</script>

二、创建RouterLink

我们同时可以通过RouterLink来进行跳转组件
内容我们可以采用slot来占位,然后采用window.history.pushState方法跳转
但是用于window.history.pushState不能触发popstate方法,所以我们需要手动触发 this.transitionTo(target)

<template>
    <a @click="jump">
        <slot></slot>
    </a>
</template>
<script>
export default {
    props:{
        to:{
            type:String,
            require:true
        }
    },
    methods: {
        jump(){
            const router=this.$root._routerRoot._router;
            router.push(this.to)
        }
    },
}
</script>

导航守卫

vue-router提供一个返回的函数,用来销毁钩子函数
首先现在router.js里写

router.beforeEach((to, from, next) => {
  console.log("beforeEach", to, from);
  next()
})
router.beforeResolve((to, from, next) => {
  console.log("beforeResolve", to, from);
  next()

})
router.afterEach((to, from) => {
  console.log("afterEach", to, from);
})

Router类添加 beforeHooks,resolveHooks ,afterHooks
registerHook会返回一个可以清除Hooks的函数

export default class Router {
    constructor({ routes = [] }) {
        this.routerTable = new RouterTable(routes)
        this.history = new H5Mode(this)
        this.beforeHooks = []
        this.resolveHooks = []
        this.afterHooks = []
    }

    init(app) {
        const { history } = this
        history.listen(route => {
            app._route = route
        })
        history.transitionTo(history.getCurrentLocation()) //app._route = route path: '/home', component
    }

    push(to) {
        this.history.push(to)
    }

    beforeEach(fn) {
        return registerHook(this.beforeHooks, fn)
    }

    beforeResolve(fn) {
        return registerHook(this.resolveHooks, fn)
    }

    afterEach(fn) {
        return registerHook(this.afterHooks, fn)
    }
}
function registerHook(list, fn) {
    list.push(fn)
    return () => {
        const i = list.indexOf(fn)
        if (i > -1) {
            list.splice(i, 1)
            console.log(list.splice(i, 1));
        }
    }
}

在base.js修改代码如下,其中runQueue会一直执行Hooks函数,并且最后会执行OnComplete函数也就updateRoute
如果没有next(),那么就不会执行step(index + 1)

import { runQueue } from '../util/async'
export default class HistoryBase {
    constructor(router) {
        this.router = router
        this.routerTable = router.routerTable
    }

    listen(cb) {
        this.cb = cb
    }

    confirmTransition(route, OnComplete, onAbort) {
        if (route === this.current) {
            return;
        }
        const queue = [
            ...this.router.beforeHooks,
            ...this.router.resolveHooks,
        ]
        const iterator = (hook, next) => {  //queue[index], () => { step(index + 1) }
            console.log(hook); //(to, from, next) => {console.log("beforeEach", to, from);next()}
            hook(route, this.current, (to) => {
                if (to === false) {
                    onAbort && onAbort(to)
                } else {
                    next(to) // step(index + 1)  next
                }
            })
        }
        runQueue(queue, iterator, () => {  //叠加器函数
            OnComplete()
        })
    }

    updateRoute(route) {
        const from = this.current
        this.current = route
        this.cb(this.current)
        this.router.afterHooks.forEach(hook => {
            hook && hook(route, from)
        });
    }

    transitionTo(target) { //pathname:/home
        const route = this.routerTable.match(target)
        this.confirmTransition(route, () => {
            this.updateRoute(route)
        })
    }
}

async.js代码如下

export function runQueue(queue, iter, end) {
    const step = (index) => {
        if (index >= queue.length) {
            end()
        } else {
            if (queue[index]) {
                iter(queue[index], () => {
                    step(index + 1)
                })
            } else {
                step(index + 1)
            }
        }
    }
    step(0)
}

总结

就是用app._route = route改变来进行组件的渲染
导航守卫就是当地址改变时,依次执行queue里面的hooks,并且递增。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值