手写vue-router(完结)

本文详细介绍了手写vue-router的过程,从location和history对象的使用,到组件注册、接口设计,再到vue-router的构造函数和初始化操作,以及安装方法的实现。重点探讨了如何确保每个组件获取到的$router和$route实例唯一,并利用Vue的响应式原理监听history变化以更新视图。同时,文中提到递归理解的拓展,强调了深入理解vue响应式原理的重要性。

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

一、location对象

location对象含有很多的属性,其中我们此次使用到的是 hash,接下来我们需要熟悉一下hash的用法。

<a href="#/index">首页</a>
<a href="#/app">app</a>
<div id="app"></div>
<script type="text/javascript">
window.addEventListener('load', ()=>{
	// 初始化url的hash模式,得到url地址,更改视图数据
	let hash = location.hash;
	hash?'':location.hash='/';
	app.innerHTML = hash.slice(1);
})
window.addEventListener('hashchange',()=>{
	// 获取到当前的值,改变视图
	let hash = location.hash.slice(1);
	app.innerHTML = hash;
})
</script>

运行结果:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、history对象

1. 常见方法

  • go(n) 从浏览器的记录中跳转跳转n页
  • back() 从浏览器的记录中向后跳转
  • forword() 从浏览器的记录中向前跳转
  • history.pushState(data, title, url);
    1. 参数1,代表自定义数据
    2. 参数2,代表网页的title,可忽略
    3. 参数3,代表新追加的历史记录

注意:

  • history必须同源,不能跨域;
  • history需要后端对所有的路由情况进行处理,否则就会404。

三、功能分析

1. 组件

我们需要注册两个组件,分别是vue-router和vue-link。两者之间通过VueRouter做调度,当vue-link或者其他方式改变了VueRouter的history属性(记录当前路由信息),那么就会触发Vue的监听,然后通知router-view来更新当前路由地址所对应的组件视图。在这里插入图片描述

2. 接口

在每一个组件上都有
$route路由信息,
在这里插入图片描述
$router路由方法
在这里插入图片描述

四、开始手写vue-router

1. 构造函数

constructor(options) {
     this.mode = options.mode || 'hash';
     this.routes = (function () {
         let res = {};
         options.routes.forEach(val=>{
             res[val.path] = val.component;
         })
         return res;
     })();
     this.history = {
         current: null
     };

     // 初始化VueRouter
     this.init();
 }

2. 初始化操作

// 初始化函数
init() {
    if (this.mode === 'hash') {
        // 初始化地址
        // console.log(location.hash);
        location.hash ? '' : location.hash = '/';
        // 当页面加载完成后将当前的地址保存
        window.addEventListener('load', () => {
            this.history.current = location.pathname;
            console.log(1,location.pathname);
        })
        // 监听页面的跳转
        window.addEventListener('hashchange', () => {
            this.history.current = location.hash.slice(1);
            console.log(2,location.hash);
        })
    } else {
        // 如果为 / 说明第一次使用,否则用户改变了url,此时不做任何操作
        location.pathname === '/' ? location.pathname = '/' : '';
        window.addEventListener('load', () => {
            this.history.current = location.pathname;
        })
        window.addEventListener('popstate', () => {
            this.history.current = location.pathname;
        })
    }
}

分析:
初始化的操作并不是很难,我们在一开始便进行了练习。

3. vue-router安装方法

我们在使用vue-router的时候都需要使用一个Vue.use(VueRouter)方法将vue-router注册到Vue中,use方法要求我们必须提供一个install方法。具体功能如下:

① 得到唯一实例

所谓的得到唯一实例是指。每个组件的this.$router和this.$route的得到的是同一个实例,具体的实现思路是递归,但是这个递归却决于Vue的渲染机制:父1->子1->孙1->父2->子2->孙2,所以代码如下:

// 确保每个组件得到的是同一个router对象
vue.mixin({
    beforeCreate() {
        // 将router挂载到根组件,因此必须确保接下来所有的this都为跟组件,即用this._root保存
        if (this.$options && this.$options.router) {
            this._root = this;
            this._router = this.$options.router;
            vue.util.defineReactive(this, 'xxx', this._root._router);
        } else {
            // 递归的思想,如果子组件没有$router属性,那么就去父组件去找,不断循环。
            this._root = this.$parent._root;
        }

        // 拦截$route属性
        Object.defineProperty(this, '$route', {
            get() {
                return {
                    current: this._root._router.history.current
                }
            }
        })
        // 拦截$router属性
        Object.defineProperty(this, '$router', {
            get() {
                return this._root._router;
            }
        })
    },

})

分析:

  • 我们使用到了Vue中的混入,混入的意思是我们可以提供一个自己的钩子函数,这些钩子函数会在我们组件的钩子执行函数之前执行。混入分为两种模式:
    1. Vue。mixin({}),这种方式是全局混入,即所有的组件都会有混入的方法。
    2. mixins: [‘mixin1’], 这是组件的私有混入。
  • 在上述代码中我们使用了vue的核心方法defineReactive(this, xxx, this._root._router)这个方法会将我们传入的this._root._router进行深度的遍历,将其转换为响应式的数据,目的是为了监听history的变换,从而通知router-view的视图更新。

③ 组件的注册

// 注册router-view组件
vue.component('router-view', {
    render(h) {
        let current = this._self._root._router.history.current;
        let component = this._self._root._router.routes[current];
        return h(component);
    }
});

// 注册router-link组件
vue.component('router-link', {
    props: ['to'],
    render(h) {
        return <a href={ this._root._router.mode === 'hash' ? `#${this.to}` : this.to }>{this.$slots.default}</a>;
    }
});

分析:

  • 我们使用了jsx的语法,读者可以自行学习

五、总结

以上基本完成了一个简易的vue-router,当然还存在很多的缺陷,希望读者见谅,在手写viue-router的过程中我也得到了一些心得体会,和读者进行分享。

  • vue-router的核心功能并不是很难,主要使用了location和history,因此我们需要充分了解这两个api
  • 在我以前对递归有一种思维定势,认为递归就是函数调用自己,但是在实现每个组件得到唯一vuerouter实例的过程中打破了我对递归的认识,递归其实也可以结合 深度优先遍历 来达到递归的作用。
  • vue的响应式原理无处不再,这也是vue的核心之一,读者需要对响应式的原理做到深入的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值