vue-router的简单实现

142 阅读1分钟

目标:

  1. 页面不刷新,hash变化,页面内容作出相应变化;
  2. $router挂载到Vue上;
  3. router-view支持多级路由的展示;

拆分任务一:

  1. 如所有vue插件一样,含有install方法,Vue.use会调用install方法;
  2. 注册全局组件router-link,router-view
    • router-link中hash处理
// index.js
import { install } from './install';
class VueRouter {
    constructor() {}
}

VueRouter.install = install;
export default VueRouter;
// install.js
import View from './View';
import Link from './Link';
export let _Vue;
export function install(Vue) {
    _Vue = Vue;

    Vue.component('RouterLink', Link);
    Vue.component('RouterView', View);
}
// Link.js
export default {
  name: 'RouterLink',
  props: {
    to: {
      type: String,
      required: true,
      default: '/'
    }
  },
  render(h) {
    return h(
      'a',
      {
        attrs: {
          href: `#${this.to}`
        }
      }, 
     this.$slots.default
    );
  }
};
// View.js
export default {
  name: 'RouterView',
  render(h) {
    return h(null);
  }
};

拆分任务二:$router挂载到Vue上

  1. 执行install的时候,VueRouter实例未创建,Vue实例也未创建,所以需要混入的方式,在beforeCreate中处理
// install.js
_Vue = Vue;
Vue.mixin({
    // 根组件new Vue会传入router,所以只有根组件会有$options.router
    if(this.$options.router) {
        Vue.prototype.$router = this.$options.router;
    }
})

拆分任务三:router-view展示内容

  1. router-view展示内容为当前hash对应路由的component
    • 监控hash变化\
    • 获取所有routes\
    • 根据当前hash值找到routes中匹配的component
// index.js
import { install, _Vue } from './install';
class VueRouter {
    constructor(options) {
        this.options = options;
        this.current = window.location.hash.slice(1) || '/';
        // current需为响应式数据,不能使用vue.set(this, xxx),因this指向VueRouter。
        _Vue.util.defineReactive(this, 'current', initial);
        window.addEventListener('hashchange', function () {
            this.current = window.location.hash.slice(1);
        });
    }
}
// View.js
export default {
  name: 'RouterView',
  render(h) {
    let component = null;
    const { current, options } = this.$router;
    const route = options?.routes?.find((item) => item.path === current);
    if (route) {
      component = route.component;
    }
    return h(component);
  }
};

拆分任务四:优化router-view,处理多级路由的展示

  1. 不需要每次render中都遍历一遍routes,来查找符合的路由
  2. new VueRouter时会传入routes,此时来处理,一遍即可
  3. VueRouter提供一个方法,可以直接在view中调用,传入当前路由,得到匹配的路由全量。
// index.js
import { createMatcher } from './match';
import { install, _Vue } from './install';class VueRouter {
    constructor(options) {
        this.options = options;
        this.match = createMatcher(options.routes || []);
        ...
export function createMatcher(routes) {
    const map = {};
    routes.forEach(r => addRoutes(map, r));

    return function match(fullPath) {
        for(const route in map) {
            if (route === fullPath) {
                return Object.freeze({
                    matched: formatMatch(map[route]);
                })
            }
        }
    }
}

// 平铺routes,key是路由path,并添加parent
function addRoutes(map, route, parent) {
    const { path, component, children } = route;
    const record = {
        path: mormallizeRoute(path, parent),
        component,
        parent
    }    if (children) {
        children.forEach(child => addRoutes(map, child, record));
    }
}

// path链接
function mormallizeRoute(path, parent) {
    if (path[0] === '/') return path;
    if (parent == null) return path;
    return `${parent.path}/${path}`.replace(////g, '/'); // 删除多于‘/’并join
}

function formatMatch(record) {
    const res = [];
    while(record) {
        res.unshift(record);
        record = record.parent;
    }
    return res;
}
// View.js
export default {
  name: 'RouterView',
  render(h) {
    let component = null;
    const { current, match } = this.$router;
    const { matched } = match(current);
    if (matched && matched[depth]) {
        component = matched[depth].component;
    }
    return h(component);
  }
};