【VueRouter 源码学习】第八篇 - $route、$router 与 router-link 组件的实现

325 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战


一,前言

上篇,介绍了路由变化时视图更新的实现,主要涉及以下内容:

  • 更新当前路由的匹配结果;
  • 路由更新前的查重;
  • 路由的响应式实现;

本篇,$route$routerrouter-link 的实现;


二,$route$router 的实现

1,前文回顾

在《【VueRouter 源码学习】第二篇 - 路由的配置和使用》中介绍过:

通过 Vue.use 注册 Router 插件时,
将在 Vue 注册两个全局组件:router-link 和 router-view;
同时,为实例提供两个原型属性:$router 和 $route;
  • $route:包含了路由相关的属性;如:name、hash、meta 等;
  • $router:包含了路由相关的方法;如:history API(push、replace、go);

2,定义原型方法 $route

在 Vue 原型上添加 $route 属性,属性值为当前匹配的路由 current 对象;

// install.js

 /**
   *  在 Vue 原型上添加 $route 属性 -> current 对象
   *  $route:包含了路由相关的属性
   */
  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      // this指向当前实例;所有实例上都可以拿到_routerRoot;
      // 所以,this._routerRoot._route 就是根实例上的 _router
      // 即:处理根实例时,定义的响应式数据 -> this.current 对象
      return this._routerRoot._route; // 包含:path、matched等路由相关属性
    }
  })

3,定义原型方法 $router

在 Vue 原型上添加 $router 属性,值为当前 router 实例;

// install.js

  /**
   *  在 Vue 原型上添加 $router 属性 -> router 实例
   *  $router:包含了路由相关的方法
   */
  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      // this._routerRoot._router 就是当前 router 实例;
      // router 实例中,包含 matcher、push、go、repace 等方法;
      return this._routerRoot._router;
    }
  });

vue-router 官方文档:路由对象&路由对象属性


三,<router-link> 组件的实现

1,创建 Link 组件对象

在 vue-router 的组件目录下,创建 Link 组件对象,模拟返回 a 标签:

// vue-router/components/link.js

export default {
  name:'routerLink',
  render(){
    return <a></a>
  }
}

在 install 阶段注册全局组件时,引入并使用 Link 组件对象:

// install.js

import Link from './components/link';
// 注册 Vue 全局组件
Vue.component('router-link', Link);

2,<router-link>组件的功能

  • 每次点击<router-link>组件时,都会进行 hash 值的切换;
  • <router-link>组件可以接收来自外部的传参;
  • <router-link>组件渲染后返回的内容包括:元素标签 + 点击跳转事件 + 插槽等;

3,<router-link>组件的实现

  • 接收外部传参 props;包含:目标路径 to 与标签名 tag;
  • 跳转方法:handler;当前路由实例下的 push 方法;
  • 渲染函数 render 方法,jsx 模板;
// vue-router/components/link.js

export default {
  // 组件名称
  name: 'routerLink',
  // 接收来自外部传入的属性
  props: {
    to: { // 目标路径
      type: String,
      required: true
    },
    tag: {  // 标签名,默认 a
      type: String,
      default: 'a'
    }
  },
  methods: {
    handler(to) {
      // 路由跳转:内部调用 history.push
      this.$router.push(to);
    }
  },
  render() {
    let { tag, to } = this;
    // JSX:标签 + 点击跳转事件 + 插槽
    return <tag onClick={this.handler.bind(this, to)}>{this.$slots.default}</tag>
  }
}

4,this.$router.push 方法的实现

this.$router.push :当前路由实例下的 push 方法:

index.js

class VueRouter {
    push(to) {
        this.history.push(to); // 子类对应的push实现
    }
}

备注:router 实例中的 history 为当前路由模式对应的子类实现,所以push方法也是子类对应的push实现;

5,this.history.push 方法的实现

实现 HashHistory 子类中的 push 方法:

// history/HashHistory.js

class HashHistory extends History {
  push(location) {
    // 跳转路径,并在跳转完成后更新 hash 值;
    // transitionTo内部会查重:hash 值变化虽会再次跳转,但不会更新current属性;
    this.transitionTo(location, () => {
      window.location.hash = location;// 更新hash值
    })
  }
}

hash 变化时,进行 transitionTo 跳转,transitionTo 的内部实现:

// history/HashHistory.js

  /**
   * 路由跳转方法:
   *  每次跳转时都需要知道 from 和 to
   *  响应式数据:当路径变化时,视图刷新
   * @param {*}} location 
   * @param {*} onComplete 
   */
  transitionTo(location, onComplete) {
    // 根据路径进行路由匹配;route :当前匹配结果
    let route = this.router.match(location);
    // 查重:如果前后两次路径相同,且路由匹配的结果也相同,那么本次无需进行任何操作
    if (location == this.current.path && route.matched.length == this.current.matched.length) { // 防止重复跳转
      return
    }
    // 使用当前路由route更新current,并执行其他回调
    this.updateRoute(route);
    onComplete && onComplete();
  }

根据当前路径进行一次路由匹配、更新前的查重、更新current为最新一次匹配到 route 对象,并执行window.location.hash = location 更新 hash;

6,视图的更新 ...

这样,点击 <router-link> 组件后,将完成路径的切换,但页面没有渲染,需要<router-view>组件的支持;


四,结尾

本篇,介绍了$route$routerrouter-link 组件的实现,主要涉及以下内容:

  • 定义原型方法 $route$router
  • <router-link>组件的功能和实现;

下一篇,<router-view>组件的实现;