美文网首页让前端飞Web前端之路程序员
前端路由探究(1) vue-router源码学习-基本数据结构

前端路由探究(1) vue-router源码学习-基本数据结构

作者: kuulid | 来源:发表于2017-12-09 20:47 被阅读72次

搭建实验环境

首先github download下来https://github.com/vuejs/vue-router vue-router最新的代码,代码在src下,结构清晰,但不适合调试看中间的一些处理结果,所以新建一个文件夹,新建index.html,创建好vue-router官网上的demo实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>Hello App!</h1>
        <p>
          <!-- 使用 router-link 组件来导航. -->
          <!-- 通过传入 `to` 属性指定链接. -->
          <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
          <router-link to="/foo">Go to Foo</router-link>
          <router-link to="/bar">Go to Bar</router-link>
        </p>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
      </div>
</body>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
    const Foo = { 
        template: '<div>foo</div>',
        created() {
            console.log('created');
        },
        methods: {
            hello() {
                console.log('hello');
            }
        }
    }
    const Bar = { template: '<div>bar</div>' }

    const routes = [
        { path: '/foo', component: Foo },
        { path: '/bar', component: Bar }
    ]

    const router = new VueRouter({
        routes // (缩写)相当于 routes: routes
    })

    const app = new Vue({
        router
    }).$mount('#app')
</script>
</html>

不同的是将官网上的CDN代码download下来到本地同文件夹下。
github下载的工程文件比较好阅读,单文件的vue-router.js可以一步步调试看数据结构,两个结合起来看。

matcher

我们打开VueRouter对象代码,有两个地方值得关注,一个是

this.matcher = createMatcher(options.routes || [], this);

另一个

  switch (mode) {
    case 'history':
      this.history = new HTML5History(this, options.base);
      break
    case 'hash':
      this.history = new HashHistory(this, options.base, this.fallback);
      break
    case 'abstract':
      this.history = new AbstractHistory(this, options.base);
      break
    default:
      {
        assert(false, ("invalid mode: " + mode));
      }
  }

可以看出一个是初始化matcher的createMatcher,打开createMatcher方法


createMatcher

我们的任务是看到vue-router是怎么处理存储路由结构的,所以应该是createRouteMap和addRoutes这个两个方法相关,但打开addRoutes,其实也是调用了createRouteMap,它是暴露给用户动态添加路由的方法。

so ,看createRouteMap,这个方法有个循环routes调用addRouteRecord,最终生成每个路由的数据结构


createRouteMap处理routers

追寻到这个方法,我们找到了每个routes的record数据结构


record结构
其中regex是通过给出的路由生成对应的正则表达式,还有对应的components,addRouteRecord还考虑了子路由的处理过程。但最终record通过对象map放到pathMap
pathMap

我们可以通过console打印出来


pathMap
而最终createRouteMap返回出来的是, 即VueRouter中matcher对象的数据结构。
createRouteMap返回结构

History

可以看到VueRouter如下生成History

  switch (mode) {
    case 'history':
      this.history = new HTML5History(this, options.base);
      break
    case 'hash':
      this.history = new HashHistory(this, options.base, this.fallback);
      break
    case 'abstract':
      this.history = new AbstractHistory(this, options.base);
      break
    default:
      {
        assert(false, ("invalid mode: " + mode));
      }
  }

分别是vue-router三种模式,具体的分别就不介绍了,官网上有。但这三个对象都是继承了基本的History类。在vue-router工程文件src/history/base.js,可以看到这个类的定义


History

简单的来说,我们操作路由router.push({ path: 'home' }),其实是在操作这个对象上的方法,而各个模式下的具体操作不同,例如hash模式下需要监听popstate和hashchange事件,调用之前matcher的match函数(具体调用在History的transitionTo中),匹配并更新window.history。具体的操作都在src/history下。
主要关注setupListeners -> transitionTo -> pushState这个几个方法的处理。可以看到最终操作window.history的是history.replaceState和history.pushState两个游览器API。

总结

通过matcher和History两个最重要的对象,VueRouter就完成了将我们定义好的路由结构转换成可以匹配的路由对象,并且添加事件监听,完成对相应路由的匹配并更新url历史。那么还有一个最重要的就是最终操作匹配到的组件并更新,这个下次写, 还有View和Link两个组件,其实也很简单,调用了Vue.util.defineReactive,定义了一个响应式对象:

Vue.util.defineReactive(this, '_route', this._router.history.current)

相关文章

网友评论

    本文标题:前端路由探究(1) vue-router源码学习-基本数据结构

    本文链接:https://www.haomeiwen.com/subject/uotpixtx.html