【VueRouter 源码学习】第七篇 - 路由变化触发视图更新

640 阅读4分钟

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


一,前言

上篇,介绍了路由匹配的实现,包括以下几个点:

  • 路由匹配的分析;
  • 路由匹配的实现:router.match、matcher.match、createRoute;

本篇,继续介绍路由变化触发视图更新;


二,视图更新的实现

1,前文回顾

前面,通过this.router.match(location)已经能够得到当前路径所匹配到的全部路由记录了;

createRoute方法,能够根据路径创建一个匹配规则:

this.current = createRoute(null, {
  path: '/'
});

2,更新当前路由的匹配结果

那么,每当路径变化时,需要更新匹配规则current:

  transitionTo(location, onComplete) {
    // 根据路径进行路由匹配;route :当前匹配结果
    let route = this.router.match(location);
    this.current = route; // 每次路由切换时,都会更改current属性
    onComplete && onComplete();
  }

3,路由更新前的查重

但是每次路径的变化,未必每次都需要执行更新操作,需要进行查重操作: (第一次 null 匹配不到结果;第二次 / 能够匹配到结果;)

  transitionTo(location, onComplete) {
    // 根据路径进行路由匹配;route :当前匹配结果
    let route = this.router.match(location);
    
    // 查重:如果前后两次路径相同,且路由匹配的结果也相同,那么本次无需进行任何操作
    if (location == this.current.path && route.matched.length == this.current.matched.length) { // 防止重复跳转
      return
    }
    
    this.current = route; // 每次路由切换时,都会更改current属性
    onComplete && onComplete();
  }

此时,当路由改变时 current 属性虽然更新了,但并不会导致视图的重新渲染;

4,路由的响应式实现

所以,需要让 history 中的 current 属性成为响应式数据,在模板中进行依赖收集,当数据变化时更新视图:

// install.js

  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {// 根组件
        this._routerRoot = this; 
        this._router = this.$options.router;
        this._router.init(this);

        // 目标:让 this._router.history.current 成为响应式数据;
        // 作用:current用于渲染时会进行依赖收集,当current更新时可以触发视图更新;
        // 方案:在根组件实例上定义响应式数据 _route,将this._router.history.current对象中的属性依次代理到 _route 上;
        // 优势:当current对象中的任何属性发生变化时,都会触发响应式更新;
        // Vue.util.defineReactive: Vue 构造函数中提供的工具方法,用于定义响应式数据
        Vue.util.defineReactive(this, '_route', this._router.history.current);
      } else { // 子组件
        this._routerRoot = this.$parent && this.$parent._routerRoot;
      }
    }
  });

在处理根组件时,定义响应式数据 _route

  • 目标:让 this._router.history.current 成为响应式数据;
  • 作用:current用于渲染时会进行依赖收集,当current更新时可以触发视图更新;
  • 方案:在根组件实例上定义响应式数据 _route,将this._router.history.current对象中的属性依次代理到 _route 上;
  • 优势:当current对象中的任何属性发生变化时,都会触发响应式更新;

当路径发生变化时,在transitionTo方法中,会更新 current 属性为当前匹配到的路由结果 route:

  transitionTo(location, onComplete) {
    let route = this.router.match(location);
    // 更新 current 属性为当前匹配到的路由结果 route
    this.current = route;
    onComplete && onComplete();
  }

更新 current,但 _router 并没有改变,这样不能触发响应式更新,需要再触发一次 _router 的更新:

// index.js#VueRouter

init(app) {
    const history = this.history;
    const setUpListener = () => {
        history.setupListener();
    }
    history.transitionTo(
        history.getCurrentLocation(),
        setUpListener
    )
    // 每次路径变化时,都会调用此方法
    // 触发根实例 app 上响应式数据 _route 的更新
    history.listen((route)=>{
        app._route = route; 
    });
}

在公用路由处理 history/base.js 中添加 listen 方法,存储路由变化时的更新回调函数:

// history/base.js

// 将
  listen(cb) {
    // 存储路由变化时的更新回调函数,即 app._route = route;
    this.cb = cb;
  }

并在路由变化时的transitionTo方法中,触发_route的更新回调:

// history/base.js

transitionTo(location, onComplete) {
    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();
  }
  listen(cb) {
    // 存储路由变化时的更新回调函数,即 app._route = route;
    this.cb = cb;
  }
  /**
   * 路由变化时的相关操作:
   *  1,更新 current;
   *  2,触发_route的响应式更新;
   * @param {*} route 当前匹配到的路由结果
   */
  updateRoute(route) {
    // 每次路由切换时,都会更改current属性
    this.current = route; 
    // 调用保存的更新回调,触发app._route的响应式更新
    this.cb && this.cb(route);
  }

app._route改变时,就会触发响应式数据更新,导致页面更新;


三,视图更新的实现

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

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

下一篇,$route$router<router-link> 组件的实现;