vue-router 源码解析+手写

502 阅读2分钟

路由的使用

  1. 初始化
  2. 定义路由组
  3. 加载路由到vue
    import VueRouter from './router.js' // 定义的路由源码
    
    Vue.use(VueRouter);
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home,
      },
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
        children: [
          {
            path: '/about/info',
            name: 'homeAbout',
            component: { render: function (createElement) {
              return createElement('h3', 'info page')
            }}
          }
        ]
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    // vue代码挂载,需要创建首页导入App组件,基本上使用过vue开发的应该能明白。
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
    

路由源码

  1. 路由功能点,需要一个router-link组件,和router-view组件
  2. router-link支持跳转,router-view需显示对应路径下的组件,且刷新页面显示。
  3. 监听浏览器路径的变化,找到匹配的组件,刷新页面,且支持嵌套路由
    import Link from "./router-link.js";
    import View from "./router-view.js";
    
    let Vue;
    
    class VueRouter {
      constructor(optins) {
        this.$options = optins; // 保存选项

        // 这里使用mixin的原因是,首先 Vue.use(VueRouter)在前 new VueRouter在后,所以在install执行时,是不存在optins的。
        // 我们就可以使用一个通用的mixin下的beforeCreate生命周期去初始化赋值一次选项即可。
        Vue.mixin({
          beforeCreate () {
            if (this.$options.router) { // 只有根组件才有$options.router参数,这样就可以避免重复赋值  
              Vue.prototype.$router = this.$options.router; // 将VueRouter的实例挂载在Vue原型上,以便在组件中使用。
            }
          }
        })

        this.currentPath = window.location.hash.slice(1) || '/'; // 获取初始路径取#后面的值
        Vue.util.defineReactive(this, "routeMatched", []); // 监听匹配路径,所需要对应渲染的组件。
        this.setMatched(); // 设置对应路径相匹配的组件

        // 监听浏览器 hash路径变化 从而进行对应处理
        window.addEventListener("hashchange", this.onHashChange.bind(this)) // 绑定当前作用域 防止丢失

      }

      onHashChange() {
        this.currentPath = window.location.hash.slice(1); // 获取最新的浏览器路径
        this.routeMatched = []; // 当路由路径变化时需要重新匹配,所以要清空之前路由匹配到的组件
        this.setMatched();
      }

      setMatched(routes) {
        routes = routes || this.$options.routes;
        for(const route of routes) {
          const { path } = route; 

          if (path === "/" && this.currentPath === "/") { // 判断是根路由
            this.routeMatched.push(route);
            return;
          }

          // 当前浏览器路径如果包含路由的路径那么直接添加组件
          if (path !== "/" && this.currentPath.indexOf(path) !== -1) {
            this.routeMatched.push(route);
            if (route.children) this.setMatched(route.children); // 如果有子路由,那么继续匹配,直至所有的组件都填充进routeMatched。
            return;
          }

        }
      }
    }
    
    VueRouter.install = (_Vue) => { // vue插件挂载时都会需要一个install方法进行初始化

      Vue = _Vue; // 保存一下vue实例对象,在VueRouter中会使用

      // 注册dom组件 router-view 和 router-link
      Vue.component("router-link", Link);
      Vue.component("router-view", View);
    }
    
    export default VueRouter
    

实现router-link

  1. 创建一个a标签,接受一个to参数即可
    export default {
      props: {
        to: { type: String, required: true }
      },
      render(h) { // 创建一个简单的a标签跳转组件
        return h("a", 
          {
            attrs: { href: "#" + this.to }
          },
          [ this.$slots.default ]
        )
      }
    }

实现router-view

  1. 核心思路,标识当前组件为router-view,递归查找父组件是否是router-view,如果是,说明当前有嵌套路由存在,我们就需要根据当前router-view是在第几层,返回对应组件即可。
    
    export default {
      render(h) {

        this.$vnode.data.routerView = true; // 标记当前组件是routerView
        let depth = 0; // 标记深度,用于嵌套路由,到底返回第几层匹配的组件
        let parent = this.$parent;

        while (parent) {
          // 查看父组件是不是routerView,如果是深度加一,直至遍历到根组件。
          const isRouterView = parent.$vnode && parent.$vnode.data.routerView;
          if (isRouterView) depth++;
          parent = parent.$parent;
        }

        let component = null;
        const route = this.$router.routeMatched[depth]; // 当前routerView在第几层,就取该层对应的组件。
        if (route && route.component) component = route.component;

        return h(component);
      }
    }

页面使用

  1. 首页
    <template>
      <div id="app">
        <div id="nav">
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link>  |
          <router-link to="/about/info">/about/info</router-link>
        </div>
        <div class="view-content">
          <router-view></router-view>
        </div>
      </div>
    </template>

2.about page 的子路由

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <router-view></router-view>
      </div>
    </template>

结语

  1. 以上代码实现了vue-router的核心思路,可以帮我们更好的理解vue-router,以及前端路由的原理。
  2. vue-router源码中还有更多细节以及更多功能点,如有想继续了解详细的同学可以前往vue-router源码进行研究。
  3. 最后再留一个小问题,每次浏览器路径变化时,我们都会使用setMatched进行遍历查找对应的组件,那么怎么样才能将结果缓存下来以便将来再次切换回使用过的路径,不需要再次遍历查找呢?