最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【Vue 故地重游】01.VueRouter 篇

    正文概述 掘金(小丫小王子)   2021-01-07   451

    写在前面

    最近有时间,准备重新学习一下 vue,工作之余整理了一下对原理及源码的收获,在此记录一下,方便以后回来复习

    ps:本篇文章以vue2.6.xvue3.2.x为例

    基本使用

    1. 使用VueRouter插件
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    
    // 1.使用VueRouter插件
    Vue.use(VueRouter);
    
    1. 创建实例
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home,
      },
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
      },
    ];
    
    // 2.创建实例
    const router = new VueRouter({
      routes,
    });
    
    1. 配置到Vue的options
    // 3.配置到选项中
    new Vue({
      // ...其他options
      router,
      render: h => h(App),
    }).$mount('#app');
    
    // 4.在组件中使用
    <router-link to="/home">home</router-link>
    <router-view></router-view>
    

    问题

    1. 为什么要先注册插件,注册插件做了哪些事情:Vue.use(VueRouter)
    2. new VueRouter(...)做了哪些事情
    3. 全局的<router-link />组件和<router-view />组件是哪里来的

    原理探究

    1.为什么要先注册插件,注册插件做了哪些事情

    1. Vue.use接收的参数是一个带有函数 install的对象,install包含参数Vue,将此参数保存,用于后续使用

      VueRouter.install = function(_Vue) {
        Vue = _Vue;
      };
      
    2. 通过mixins延迟执行,取出根实例中的router,挂载到Vue原型链

      Vue.mixin({
        beforeCreate() {
          // 这个钩子函数在每个组件创建实例时都会调用,但只有根组件被加入了router
          if (this.$options.router) {
            Vue.prototype.$router = this.$options.router;
          }
        },
      });
      
    3. 全局声明router-link组件和router-view组件

      • 3.1. router-link组件:只做展示,点击时更新 url this.$slots.default是一个虚拟dom构成的数组
        Vue.component('router-link', {
          props: {
            to: {
              type: String,
              required: true,
            },
          },
          render(h) {
            return h(
              'a',
              {
                attrs: {
                  href: '#' + this.to,
                },
              },
              this.$slots.default // 虚拟dom构成的数组
            );
          },
        });
        
      • 3.2. router-view组件:url 变化时,展示的组件对应改变,Vue 最大的特点是响应式,而组件每次渲染都是调用了render函数,因此我们希望将来某个数据变化时能够触发render执行,这部分内容在new VueRouter(...)时操作

    2.new VueRouter(...) 做了哪些事情

    new VueRouter(...)返回的实例将来要挂载到 Vue原型链上,即组件中用的this.$router

    1. 定义一个响应式的属性current,改变时触发 <router-view /> 更新

    2. Vue 提供了定义响应式数据的方法:Vue.util.defineReactive

      这个方法会给我们的属性添加observer, 然后通过 getter函数 触发watcher的收集机制,将<router-view />的 render 方法收集到依赖中, 这样每次更新时就会执行render函数,从而更新视图。

      核心代码如下:

      // 定义响应式数据
      Vue.util.defineReactive(this, 'current', initial);
      
      Vue.component('router-view', {
        render(h) {
          let component = null;
          // 调用一次getter,观察者会将render函数收集
          const route = this.$router.$options.routes[this.$router.current];
          if (route) {
            component = route.component;
          }
      
          return h(component);
        },
      });
      
    3. 监听路由改变

      window.addEventListener('hashchange', () => {
        this.current = window.location.hash.slice(1);
      });
      

      因为此时current已经是响应式数据,所以改变时会自动触发watcher更新:即<router-view />更新

      到这里,VueRouter的基本原理就结束了。

    思考与总结

    1. 定义响应式数据的方式除了Vue.util.defineReactive还有哪些,有何区别?

    • 1.1. 创建一个 Vue 对象

      const state = new Vue({
        data: { count: 0 },
      });
      

      state.count就是响应式对象,可以直接在视图中使用

    • 1.2. Vue.observable

      const state = Vue.observable({ count: 0 });
      

      同样state.count是一个响应式对象,直接在视图中使用

    • 1.3. 区别:defineReactiveVue源码中的内容,并没有在官方文档中出现过,而observable2.6版本新出现的一个API,在低于 2.6 版本的 Vue 项目 中会报is not a function的错误。所以保险起见,使用defineReactivenew Vue(...)比较好

    2. mixins的妙用:延迟执行

    • 2.1. 这里是给全局Vue混入生命周期,因此每个组件都享用该mixins,需要在执行函数中加以判断,只在根组件中挂载$router

      Vue.mixin({
        beforeCreate() {
          // 根实例才有该选项
          if (this.$options.router) {
            Vue.prototype.$router = this.$options.router;
          }
        },
      });
      
    • 2.2. 由于混入组件的内容与当前组件容易出现命名冲突、以及方法变量来源不明,一直以来都对这个 api 绕着走。今天才发现了原来还有这么妙的使用方式,最开始以为是借助事件总线实现延迟执行,直到看了源码的这部分,才有了恍然大悟的感觉

    3. 嵌套<router-view/>如何解决

    源码是这样处理的:

    VueRouter 中新增一个响应式属性matched,每次路由变化时执行一个叫做match的函数用来匹配当前路由。

    <router-view/>组件中,给虚拟 dom 的data中添加一个属性routerView,表示当前组件是<router-view />

    然后在 render 函数中,创建一个变量depth表示当前路由的深度,其计算方式是通过while循环向上遍历,遇到<router-view />+1

    最后,<router-view/>组件渲染时从$router中取出matched,根据matched[depth]就可以获取到当前 url 下对应深度的路由组件

    核心代码:

    VueRouter.js

    constructor(options) {
      this.$options = options;
    
      // 表示当前url
      this.current = window.location.hash.slice(1) || '/';
      // 定义一个响应式数组 matched,用来存储当前匹配的路由
      Vue.util.defineReactive(this, 'matched', []);
      this.match();
    
      // 每次hash改变都重置matched,然后重新匹配
      window.addEventListener('hashchange', () => {
        this.current = window.location.hash.slice(1);
        this.matched = [];
        this.match();
      });
    }
    
    match(routes) {
      routes = routes || this.$options.routes;
    
      for (const route of routes) {
        if (route.path === '/' && this.current === '/') {
          this.matched.push(route.component);
        } else if (route.path !== '/' && this.current.includes(route.path)) {
          this.matched.push(route.component);
        }
        if (route.children) {
          this.match(route.children);
        }
      }
    }
    

    router-view (ps:源码中是函数式组件,这里为了简化就直接用非函数组件了)

    Vue.component('router-view', {
      render(h) {
        // <router-view /> 的标志
        this.$vnode.data.routerView = true;
    
        // 嵌套路由
        let depth = 0;
        let parent = this.$parent;
        while (parent) {
          const vnodeData = parent.$vnode ? parent.$vnode.data : {};
          if (vnodeData.routerView) {
            depth++;
          }
          parent = parent.$parent;
        }
    
        const component = this.$router.matched[depth];
    
        return component ? h(component) : null;
      },
    });
    

    4. 当前webpack环境在Vue.component中为什么不能用template?

    在写router-view组件时,发现template无法使用,虽然不是VueRouter的内容,不过遇到了就总结下吧 在webpack环境中,默认情况下打包的 vueruntime 版本,借助loader就完成了编译工作,并且runtime识别的是render 函数,因此没有 compiler模块,也就不能解析template


    起源地下载网 » 【Vue 故地重游】01.VueRouter 篇

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元