学习笔记 手写VueRouter 实现相关功能

1. 准备项目环境

一、初始化项目

使用 vue脚手架 创建项目

vue create my-vuerouter

等待项目初始化完成,进行项目的运行

npm run serve

二、保留有用的代码

Home.vue

<template>
  <div class="home">
    <HelloWorld msg="Welcome to Your Vue.js App" />
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}
</script>

HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

2. 实现vuerouter

一、创建文件

src 下创建vuerouter/index.js

二、编写代码

1. 实现 install 静态方法

install 方法需要实现以下功能:

​ a. 判断当前插件是否已经安装

​ b. 把 Vue 的构造函数记录在全局

​ c. 把创建 Vue 的实例传入的 router 对象注入到 Vue 实例

判断当前插件是否已经安装
//1 判断当前插件是否被安装
if(VueRouter.install.installed){
    return;
}
VueRouter.install.installed = true
Vue 的构造函数记录在全局
let _Vue = null // 定义一个全局变量来记录Vue构造函数


//2 把Vue的构造函数记录在全局
_Vue = Vue
把创建 Vue 的实例传入的 router 对象注入到 Vue 实例
//3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
    beforeCreate(){
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if(this.$options.router){
            _Vue.prototype.$router = this.$options.router
        }
    }
})
完整的 install 静态方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }
}

2. 实现 constructor 构造函数

let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

}

3. 实现 createRouteMap 方法

let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }
}

4. 实现 initComponents 方法中的 router-link方法

let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }
    
  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      template: '<a :href="to"></a>'
    })
  }
}

5. 实现 init 方法

测试 createRouteMapinitComponent 方法,需要在创建 Vue 的实例时,执行这两个方法

let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  init () {
    createRouteMap()
    initComponent(_Vue)
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      template: '<a :href="to"><slot></slot></a>'
    })
  }
}

修改 router/index.js
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '../vuerouter'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

测试🌰
npm run server

打开网页会看不到效果,在控制台中有两个报错

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
vue 的构建版本

​ **运行时版:**不支持 template 模版,需要打包的实收提前编译

​ **完整版:**包含运行时和编译器,体积比运行时版大 10k 左右,程序运行的时候把模板转换成 render 函数

原因: vuecli 创建时使用 运行时版本

解决方案1:使用完整版的 Vue 参考资料

vue.config.js

module.exports = {
  runtimeCompiler: true,
  // 取消 eslint 校验
  lintOnSave: false,
  devServer: {
    overlay: {
      warning: false,
      errors: false
    }
  },
}

再次执行 npm run serve,就能正常显示的两个链接了

解决方案2:initComponents 方法中实现 render 函数
let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  init () {
    this.createRouteMap()
    this.initComponent(_Vue)
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      render (h) {
        return h("a", {
          attrs: {
            href: this.to
          },
        }, [this.$slots.default])
      },
      // template: '<a :href="to"><slot></slot></a>'
    })
  }
}

6. initComponents 方法中实现 router-view 方法

增加 router-view 方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  init () {
    this.createRouteMap()
    this.initComponent(_Vue)
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      render (h) {
        return h("a", {
          attrs: {
            href: this.to
          },
        }, [this.$slots.default])
      },
      // template: '<a :href="to"><slot></slot></a>'
    })

    const self = this
    Vue.component("router-view", {
      // 创建虚拟 dom
      render (h) {
        // 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
        // self.data.current
        const component = self.routeMap[self.data.current]
        return h(component) // 返回虚拟 dom
      }
    })
  }
}

这时候测试发现,点解链接时会发送 http 请求,为了阻止发送 http 请求,需要在组件中注册点击事件来阻止发送请求

完善router-link方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  init () {
    this.createRouteMap()
    this.initComponent(_Vue)
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      render (h) {
        return h("a", {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickhander
          }
        }, [this.$slots.default])
      },
      // template: '<a :href="to"><slot></slot></a>'
      methods: {
        clickhander (e) {
          // 改变地址栏
          history.pushState({}, "", this.to)
          // 加载路径对应组件
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
    })

    const self = this
    Vue.component("router-view", {
      // 创建虚拟 dom
      render (h) {
        // 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
        // self.data.current
        const component = self.routeMap[self.data.current]
        return h(component) // 返回虚拟 dom
      }
    })
  }
}

7. 实现 initEvent 方法

解决 history 发生变化的时候,渲染对应的组件

let _Vue = null // 定义一个全局变量来记录Vue构造函数

export default class VueRouter {
  static install (Vue) {
    // 1. 判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true

    // 2. 把 Vue 的构造函数记录在全局
    _Vue = Vue

    // 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor(options) {
    this.options = options
    this.routeMap = {}
    // observable
    this.data = _Vue.observable({
      current: "/"
    })
  }

  init () {
    this.createRouteMap()
    this.initComponent(_Vue)
    this.initEvent()
  }

  createRouteMap () {
    // 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    });
  }

  initComponent (Vue) {
    Vue.component("router-link", {
      props: {
        to: String
      },
      render (h) {
        return h("a", {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickhander
          }
        }, [this.$slots.default])
      },
      // template: '<a :href="to"><slot></slot></a>'
      methods: {
        clickhander (e) {
          // 改变地址栏
          history.pushState({}, "", this.to)
          // 加载路径对应组件
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
    })

    const self = this
    Vue.component("router-view", {
      // 创建虚拟 dom
      render (h) {
        // 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
        // self.data.current
        const component = self.routeMap[self.data.current]
        return h(component) // 返回虚拟 dom
      }
    })
  }

  initEvent () {
    // this 是 vuerouter 对象
    window.addEventListener("popstate", () => {
      this.data.current = window.location.pathname
    })
  }
}

测试点解浏览器的返回按钮会渲染上一个路径的组件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值