vue暴露的全局方法_Vue全家桶之vuerouter手写实现

经过自己平时的学习,也因为自己现在也是从事的Vue的开发,所以平时对Vue相对来说还是较为熟悉,但是平时自己一直有一个很大的痛点,那就是对Vue的底层实现原理等等这些不够了解,所以很多时候定位错误都可能需要花费很多时间,所以深度学习并且掌握一门框架的核心实现是非常有必要的。

主题:学习Vue的全家桶vue-router的实现原理和步骤

目标:清晰思想,了解步骤,动手实现简化版

预备知识:

  • Vue的插件实现写法

  • render函数的使用

  • 数据响应式方法:Vue.util.defineReactive / new Vue ( { data: {} } )

一、预备知识回顾

1.Vue的插件实现写法:Vue的插件一般是用来为Vue来添加全局功能的,可以用来添加全局方法或者属性、添加全局资源(指令/过滤器等等)、通过全局混入来添加一些组件的选项以及可以添加Vue的实例方法。

  • 插件的声明:Vue的插件需要实现暴露一个install方法,方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象

MyPlugin.install = function (Vue, options) {    // 1.添加全局方法或者属性    Vue.globalMethod = function () {}       // 2.添加全局资源    Vue.directive('my-directive', {})     // 3.混入注入组件选项    Vue.mixin({        created: function (){}    })    // 4.添加实例方法    Vue.prototype.$myMethod = function(methodOptions){}}
  • 插件的使用

插件的使用可以有2种形式1.第一种那就是在实现的插件代码里面加上代码:if (typeof window !== 'undefined' && window.Vue) {    // 使用插件    window.Vue.use(MyPlugin)}2.第二种就是可以直接在main.js中直接使用Vue.use(MyPlugin)

2.render函数的使用:Vue推荐在日常使用中应该使用模板来创建HTML,但是有的时候需要JS的能力,那么这时候就可以使用渲染函数来完成

  • 基础

render: function (createElement) {    // createElement函数返回结果是VNode    return createElement(        tag,     // 标签名称        data,   // 传递数据        children // 子节点数组    )}
  • 拓展:为什么常见的使用的是render(h)?

Why:从例子中我们可以看到我们常见的都是使用的render(h),通过学习发现,其实在Vue的底层在生成虚拟DOM方面其实是借鉴的Snabbdom.js,而在这个里面他的生成虚拟DOM的函数就叫h,所以其实h就是我们上面看到的createElement

// 例子heading组件// {{title}}Vue.component('heading', {    props: {        title: {            type: String,            default: ''        }    },    render(h) {        return h(        'h2',    // 参数1:tagname        {attrs: {title: this.title} }  // 参数2:与模板中属性对应的数据对象        this.$slots.default  // 参数3:子节点VNode数组        )    }})

3.数据响应式方法:Vue.util.defineReactive / new Vue ({data: {}})

我们知道Vue最大的亮点之一就是响应式,凡是写入data里面的都会被变成响应式数据,而Vue2.x当中实现响应式就是使用的defineReactive,来看下具体的用法:

// defineReactive:定义一个对象的响应属性    Vue.util.defineReactive(obj, key, value, fn)    obj:目标对象    key:目标对象属性    value:属性值    fn:只在node调试环境下set时调用

其实Vue.util中还有一些函数,如:

d03dbd7302f3e233d30bd930f14daa0f.png

二、Vue全家桶之Vue-router的手动实现

    1.在我们手动实现vue-router之前,我们先看一下他在Vue中的使用

  • 安装

vue add router
  • 核心步骤:

    • 使用vue-router插件(router.js)

import Router from 'vue-router'Vue.use(Router)
  • 创建Router实例(router.js)

export default new Router({...})
  • 在根组件上添加该实例(main.js)

import router from './router'new Vue({    router}).$mount('#app')
  • 添加路由视图(App.vue)

  • 导航

"/">home"/about">aboutthis.$router.push('/')this.$router.push('/about')

2.我们发现作为Vue全家桶之一的vue-router使用起来非常方便,帮助我们很好的处理了路由的跳转的问题,那么现在我们就来看看怎么实现一个简化版的vue-router,从而更加了解他的内部原理

f76553a63a7b3bc2489aa13dcdc33cfd.png

4.代码编写:

  • 实现一个插件:创建VueRouter类和install方法

    • 一个问题:我们在install的方法中为什么要使用mixin混入的形式?

我们在使用vue-router的时候我们可以发现,我们是先使用use注册的,然后再去创建的Router实例,但是我们在写install方法的时候又需要用到,所以我们只能只用mixin来做延迟处理,等到有了组件实 例之后我们再进行使用

// 新建一个kvue-router.js// 引用构造函数,VueRouter中要使用let Vue// 保存用户的选项class VueRouter() {    constructor(options){        this.$options = options    }}// 实现VueRouter的install方法,注册$router在Vue的原型之上VueRouter.install = function(_Vue) {    // 引用构造函数,VueRouter要使用    Vue = _Vue    Vue.mixin({        beforeCreate(){            if(this.$options.router) {                // 将$router挂载到Vue的原型之上,方便所有的组件都可以使用this.$router                Vue.prototype.$router = this.$options.router            }        }    })    // 实现俩个全局的组件router-link和router-view    Vue.component('router-link', RouterLink)    Vue.component('router-view', RouterView)}export default VueRouter
  • 创建router-view和router-link

// router-linkVue.component('router-link', {    props: {        to:{            type: String,            required: true        }    },    render(h) {        return h(            'a',            attrs: { href: '#'+ this.to },            this.$slots.default        )    }})// router-viewVue.component('router-view', {    render(h) {        return h(null)    }})
  • 监控URL变化:定义响应式的current属性,监听hashChange事件

class VueRouter(){    constructor(options) {        // 定义一个响应式的数据current表示路由的变化        const initial = window.location.hash.slice(1) || '/'        // Vue中的响应式方法        Vue.defineReactive(this, 'current', initial)        //  监听hashChange事件        window.addEventListener('hashChange', this.onHashChange.bind(this))    }    onHashChange(){        this.current = window.location.hash.slice(1)    }}
  • 动态获取对应组件

// router-viewVue.component('router-view', {    render(h) {        // 动态的获取组件,进行内容的渲染        let component = null        const route = this.$router.$options.routes.find(route => route.path === this.$router.current)        if(route) component = route.component        return h(component)    }})
  • 提前处理路由表:写到这里我们可以发现其实我们已经实现了整个vue-router的功能,我们去用我们写好的kvue-router去替换掉vue-router,发现是可以实现我们想要的功能的,但是从上一步我们动态获取对应组件的逻辑里面我们不难发现,如果直接就这样写是会有性能问题的,我们会发现只要路由一变化,render就会执行一遍,这时候里面的遍历循环也会相应执行一遍,所以这个地方我们需要做一下优化,我们可以提前去处理好path和route的映射关系,这样就可以避免每次都要循环了

class VueRouter {    constructor(options) {        // 缓存path和route映射关系        this.routeMap = {}        this.$options.routes.forEach(route => {            this.routeMap[route.path] = route        });    }}// router-viewexport default {    render(h) {        const {routeMap, current} = this.$router        const component = routeMap[current] ? routeMap[current].component : null;        return h(component);    }}

四、总结:

    通过学习,Vue全家桶之一的Vue-router就算是手撸完成了,虽然只是简单版本的,但是感觉对于自己来说也是一个小的进展,基本原理基本清晰了,而且写完之后再去看源代码也清晰容易了不少,然后现在写完的这个简单的版本其实还差了一块,那就是没有实现子路由的嵌套,子路由的嵌套的逻辑上是路由嵌套,但是物理上其实就是router-view的嵌套,通过后续翻看源码发现,源码的思路是:先给所有的router-view组件的data都设置一个routerView属性,然后设置一个变量depth路由深度,然后去遍历,如果parent的$vnode.data里的routerView属性是true的话,depth就++,然后因为每次URL匹配的时候,结果都是一个数组,从根开始依次按层级放入,然后我们通过route.matched[depth]就可以知道该渲染哪一层的component的了。

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页