下面是一段简单的代码,通过这段代码分析一下如何去实现一个自己的 vue-router,并且思考下面两个问题:

思考1:为什么要在创建 vue 实例时指定 router? 思考2:如何实现 router-view 和 router-link 这两个组件以及这两个组件的作用是什么?

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. const layout = () => import('@/pages/layout')
  4. const home = () => import('@/pages/home')
  5. Vue.use(Router)
  6. const redirect = {
  7. path: '*',
  8. redirect: '/'
  9. }
  10. let router = new Router({
  11. mode: 'history',
  12. routes: [
  13. {
  14. path: '/',
  15. name: 'layout',
  16. component: layout,
  17. redirect: '/home',
  18. children: [
  19. {
  20. path: 'home',
  21. name: 'home',
  22. component: home,
  23. meta: {
  24. title: '首页',
  25. noLogin: true
  26. }
  27. }
  28. ]
  29. },
  30. redirect
  31. ]
  32. })
  33. export default router
  34. // login.vue
  35. <router-link :to="{name: 'home'}"></router-link>
  36. // app.vue
  37. <router-view/>
  38. // main.js
  39. import router from './router'
  40. new Vue({
  41. el: '#app',
  42. router,
  43. store,
  44. components: { App },
  45. template: '<App/>'
  46. })

经过分析我们得出 Vue Router 需要满足下面四个基本功能:

  • 作为 Vue 插件 — 实现 install 方法
  • 监听url变化,渲染对应的组件
  • 路由配置解析,{ path: '/', component: 'home'} => '/' —> home
  • 实现两个全局组件:router-link、route-view

实现插件功能

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。

  1. // 实现 install 方法
  2. VueRouter.install = function(Vue) {
  3. // 全局混入
  4. Vue.mixin({
  5. beforeCreate() {
  6. // this 是 Vue 实例
  7. if (this.$options.router) {
  8. // 仅在根组件执行一次
  9. Vue.prototype.$router = this.$options.router;
  10. this.$options.router.init();
  11. }
  12. }
  13. });
  14. };

创建 Router 类

  1. import Vue from "vue";
  2. class VueRouter {
  3. constructor(options) {
  4. this.$options = options;
  5. this.routeMap = {};
  6. // 通过 Vue 实现路由响应式,所以 Vue Router 只能用于 Vue 中,它们之间是强绑定的
  7. this.app = new Vue({
  8. data: {
  9. current: "/"
  10. }
  11. });
  12. }
  13. init() {
  14. this.bindEvents();
  15. this.createRouteMap(this.$options);
  16. this.initComponent();
  17. }
  18. // 监听url变化,渲染对应的组件
  19. bindEvents() {
  20. window.addEventListener("load", this.onHashChange.bind(this));
  21. window.addEventListener("hashchange", this.onHashChange.bind(this));
  22. }
  23. // hash 值改变时,current 值也跟着改变
  24. onHashChange() {
  25. this.app.current = window.location.hash.slice(1) || "/";
  26. }
  27. // 解析路由配置
  28. createRouteMap(options) {
  29. options.routes.forEach(item => {
  30. this.routeMap[item.path] = item.component;
  31. });
  32. }
  33. // 实现两个全局组件 router-link、router-view,当组件实例中的 current 变化时,这两个全局组件都会重新渲染
  34. initComponent() {
  35. Vue.component("router-link", {
  36. props: { to: String },
  37. render(h) {
  38. // h(tag, data, children)
  39. return h("a", { attrs: { href: "#" + this.to } }, [
  40. this.$slots.default
  41. ]);
  42. }
  43. });
  44. Vue.component("router-view", {
  45. // 使用箭头函数, 通过 this 获取 routeMap 中对应 current 的组件
  46. render: h => {
  47. const comp = this.routeMap[this.app.current];
  48. return h(comp);
  49. }
  50. });
  51. }
  52. }

思考解答

  1. // 思考1
  2. 保证通过router对象仅在根组件的时候挂载和执行init方法
  3. // 思考2
  4. 使用 Vue.component 注册为全局组件
  5. router-link:改变 hash 值,触发浏览器的历史状态管理
  6. route-view:根据 hash 值,渲染对应的组件

完整代码

  1. import Home from "./views/Home";
  2. import About from "./views/About";
  3. import Vue from "vue";
  4. class VueRouter {
  5. constructor(options) {
  6. this.$options = options;
  7. this.routeMap = {};
  8. // 通过 Vue 实现路由响应式,所以 Vue Router 只能用于 Vue 中
  9. this.app = new Vue({
  10. data: {
  11. current: "/"
  12. }
  13. });
  14. }
  15. init() {
  16. this.bindEvents();
  17. this.createRouteMap(this.$options);
  18. this.initComponent();
  19. }
  20. // 监听url变化,渲染对应的组件
  21. bindEvents() {
  22. window.addEventListener("load", this.onHashChange.bind(this));
  23. window.addEventListener("hashchange", this.onHashChange.bind(this));
  24. }
  25. onHashChange() {
  26. this.app.current = window.location.hash.slice(1) || "/";
  27. }
  28. // 解析路由配置
  29. createRouteMap(options) {
  30. options.routes.forEach(item => {
  31. this.routeMap[item.path] = item.component;
  32. });
  33. }
  34. // 实现两个全局组件 router-link、router-view,当组件实例中的 current 变化时,这两个全局组件都会重新渲染
  35. initComponent() {
  36. Vue.component("router-link", {
  37. props: { to: String },
  38. render(h) {
  39. // h(tag, data, children)
  40. return h("a", { attrs: { href: "#" + this.to } }, [
  41. this.$slots.default
  42. ]);
  43. }
  44. });
  45. Vue.component("router-view", {
  46. // 使用箭头函数, 通过 this 获取 routeMap 中对应 current 的组件
  47. render: h => {
  48. const comp = this.routeMap[this.app.current];
  49. return h(comp);
  50. }
  51. });
  52. }
  53. }
  54. // 实现 install 方法
  55. VueRouter.install = function(Vue) {
  56. // 全局混入
  57. Vue.mixin({
  58. beforeCreate() {
  59. // this 是 Vue 实例
  60. if (this.$options.router) {
  61. // 仅在根实例执行一次 (main.js中 new Vue 时)
  62. Vue.prototype.$router = this.$options.router;
  63. this.$options.router.init();
  64. }
  65. }
  66. });
  67. };
  68. Vue.use(VueRouter);
  69. export default new VueRouter({
  70. routes: [
  71. { path: "/", component: Home },
  72. { path: "/about", component: About }
  73. ]
  74. });