10分钟教你 手写 Vue-Router

本文详细介绍了如何在Vue项目中使用自定义的VueRouter实现路由规则、组件管理和解决VueCLI运行时模板问题,包括使用`routeMap`、`router-link`、`router-view`以及处理模板编译和事件监听等关键部分。
摘要由CSDN通过智能技术生成

再次我们不需要过多的操作,只需要声明 3 个属性

  1. this.options = options 记录构造函数中传入的选项 options

  2. this.routeMap = {} 把 options 中传入的 routes 也就是路由规则解析出来,键值对的形式,键---路由地址,值---路由组件

  3. this.data = _Vue.observable({ current: ‘/’ })} observable 创建响应式对象,对象中存储当前路由地址,默认是 /

constructor (options) {

// 记录构造函数中传入的选项 options

this.options = options

// 把 options 中传入的 routes 也就是路由规则解析出来,键值对的形式,键—路由地址,值—路由组件

this.routeMap = {}

// data 是一个响应式对象,因为 data 中存储的是当前路由地址,路由变化时要自动加载组件

this.data = _Vue.observable({ // observable 创建响应式对象

current: ‘/’ // 存储当前路由地址,默认是 /

})

}

createRouteMap

把构造函数中传过来的 选项中的 routes 也就是路由规则转换为键值对的形式转换到routeMap对象中 键---路由地址,值---路由组件

createRouteMap () {

// 遍历所有的路由规则,以键值对的形式存储在 routeMap 对象中

this.options.routes.forEach(route => {

this.routeMap[route.path] = route.component

})

}

initComponents

该方法用来创建 router-link 与 router-view

router-link

initComponents (Vue) {

Vue.component(‘router-link’, {

props: {

to: String

},

template: ‘’

})

}

注意点
  • 如果我们使用 vue_cli 创建项目并且运行时,使用上述创建方法会出现一些报错,原因在于创建标签时使用了 template

  • 而 vue_cli 使用的 运行时版的 vue,不支持 template 模板,需要打包的时候提前编译

解决方法有两种

  1. 修改 vue.config.js 配置使用完整版的 vue

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

module.exports = {

// 渲染完整版 vue

runtimeCompiler: true

}

  1. 使用 render 函数

运行时版本的 vue 不带编译器,所以不支持组件中的 template 选项,编译器的作用就是把 template 编译成 render 函数,运行时的组件可以直接写 render 函数

单文件组件时一直使用 template 没写 render 是因为在打包的过程中把单文件的 template 编译成 render 函数了,这叫做预编译

render
  • render 函数接受一个参数,通常叫做 h 作用是帮我们创建虚拟 DOM,h 由 vue 传递

  • h 函数接受 3 个参数

  1. 创建这个元素的选择器。

  2. 给标签设置一些属性,如果是 DOM 对象的属性,需要添加到 attrs 中。

  3. 生成的元素的子元素,所以是数组形式

initComponents (Vue) {

Vue.component(‘router-link’, {

props: {

to: String

},

/*

运行时版本的 vue 不带编译器,所以不支持组件中的 template 选项,编译器的作用就是把 template 编译成 render 函数,运行时的组件可以直接写 render 函数

单文件组件时一直使用 template 没写 render 是因为在打包的过程中把单文件的 template 编译成 render 函数了,这叫做预编译

*/

render (h) { // 该函数接收一个参数,通常叫做 h 作用是帮我们创建虚拟 DOM,h 由 vue 传递

return h(‘a’, { // h 函数接受 3 个参数,1. 创建这个元素的选择器。2. 给标签设置一些属性,如果是 DOM 对象的属性,需要添加到 attrs 中。3. 生成的元素的子元素,所以是数组形式

attrs: {

href: this.to

}

}, [this.$slots.default])

}

// template: ‘’

})

}

router-view

const self = this

Vue.component(‘router-view’, {

// 获取到当前路由地址对应的路由组件

render (h) {

const component = self.routeMap[self.data.current]

return h(component)

}

})

集合上述方法 检查是否存在问题

  • 因为我们默认只会调用 install 方法,但是我们还有一些其他方法需要调用,还需要一个 init 方法,在 install 方法 被调用后,调用 createRouteMap() 与 initComponents()

init () {

this.createRouteMap()

this.initComponents(_Vue)

}

  • 将上述所有方法整合(为了便于阅读,去掉注释)

// …/vuerouter/index.js

let _Vue = null

export default class VueRouter {

static install (Vue) {

if (VueRouter.install.installed && _Vue === true) return

VueRouter.install.installed = true

_Vue = Vue

Vue.mixin({

beforeCreate () {

if (this.$options.router) {

_Vue.prototype. r o u t e r = t h i s . router = this. router=this.options.router

this.$options.router.init()

}

}

})

}

constructor (options) {

this.options = options

this.routeMap = {}

this.data = _Vue.observable({

current: ‘/’

})

}

init () {

this.createRouteMap()

this.initComponents(_Vue)

this.initEvent()

}

createRouteMap () {

this.options.routes.forEach(route => {

this.routeMap[route.path] = route.component

})

}

initComponents (Vue) {

Vue.component(‘router-link’, {

props: {

to: String

},

render (h) {

return h(‘a’, {

attrs: {

href: this.to

}

}, [this.$slots.default])

}

})

const self = this

Vue.component(‘router-view’, {

render (h) {

const component = self.routeMap[self.data.current]

return h(component)

}

})

}

}

// 记得替换 router/index.js 中引入的 VueRouter

import Vue from ‘vue’

// import VueRouter from ‘vue-router’

import VueRouter from ‘…/vuerouter’

import Home from ‘…/views/Home.vue’

import Home from ‘…/views/About.vue’

Vue.use(VueRouter)

const routes = [

{

path: ‘/’,

name: ‘Home’,

component: Home

},

{

path: ‘/about’,

name: ‘About’,

component: About

}

]

const router = new VueRouter({

routes

})

export default router

  • 实际测试后,我们会发现,上述仍然存在一些小问题,也就是我们在点击 router-link 时,会改变路径刷新页面,但是在单页面组件中,我们是不需要刷新页面的,所以我们需要对 router-link 在做一点小调整

完善 router-link

  • 我们需要给 a 标签添加 1 个 方法,这个方法有三个作用
  1. 通过 pushState 方法 改变浏览器地址栏,但不给服务器发请求

  2. 将当前路由路径 同步给默认值

  3. 阻止标签默认事件

pushState 方法接受 3 个参数

  1. data。
  1. title 网页的标题。
  1. url 地址。

Vue.component(‘router-link’, {

props: {

to: String

},

render (h) {

return h(‘a’, {

attrs: {

href: this.to

},

on: {

click: this.clickHandler

}

}, [this.$slots.default])

},

methods: {

clickHandler (e) {

// 1. 通过 pushState 方法 改变浏览器地址栏,但不给服务器发请求

history.pushState({}, ‘’, this.to) // 该方法接受 3 个参数,1. data。2. title 网页的标题。3. url 地址

this.$router.data.current = this.to

e.preventDefault()

}

}

})

最终的完善

将上边的 router-link 完善后,我们的 Vue-Router 就实现了,但是还差一个小功能,也就是浏览器左上角的前进后退就失效了,所以我们需要在添加一个小方法来实现最终的完善

  • 其实就是通过 监听 popstate 将当前的路由路径赋值给 current 来达到左上角的前进后退功能**(记得将其添加到 init 方法中)**

initEvent () {

window.addEventListener(‘popstate’, () => {

this.data.current = window.location.pathname

})

}

  • 最终完整版代码(分为两版,有注释的在最后)

let _Vue = null

export default class VueRouter {

static install (Vue) {

if (VueRouter.install.installed && _Vue === true) return

VueRouter.install.installed = true

_Vue = Vue

Vue.mixin({

beforeCreate () {

if (this.$options.router) {

_Vue.prototype. r o u t e r = t h i s . router = this. router=this.options.router

this.$options.router.init()

}

}

})

}

constructor (options) {

this.options = options

this.routeMap = {}

this.data = _Vue.observable({

current: ‘/’

})

}

init () {

this.createRouteMap()

this.initComponents(_Vue)

this.initEvent()

}

createRouteMap () {

this.options.routes.forEach(route => {

this.routeMap[route.path] = route.component

})

}

initComponents (Vue) {

Vue.component(‘router-link’, {

这里分享一份由字节前端面试官整理的「2021大厂前端面试手册」,内容囊括Html、CSS、Javascript、Vue、HTTP、浏览器面试题、数据结构与算法。全部整理在下方文档中,共计111道

HTML

  • HTML5有哪些新特性?

  • Doctype作⽤? 严格模式与混杂模式如何区分?它们有何意义?

  • 如何实现浏览器内多个标签页之间的通信?

  • ⾏内元素有哪些?块级元素有哪些? 空(void)元素有那些?⾏内元 素和块级元素有什么区别?

  • 简述⼀下src与href的区别?

  • cookies,sessionStorage,localStorage 的区别?

  • HTML5 的离线储存的使用和原理?

  • 怎样处理 移动端 1px 被 渲染成 2px 问题?

  • iframe 的优缺点?

  • Canvas 和 SVG 图形的区别是什么?

JavaScript

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 问:0.1 + 0.2 === 0.3 嘛?为什么?

  • JS 数据类型

  • 写代码:实现函数能够深度克隆基本类型

  • 事件流

  • 事件是如何实现的?

  • new 一个函数发生了什么

  • 什么是作用域?

  • JS 隐式转换,显示转换

  • 了解 this 嘛,bind,call,apply 具体指什么

  • 手写 bind、apply、call

  • setTimeout(fn, 0)多久才执行,Event Loop

  • 手写题:Promise 原理

  • 说一下原型链和原型链的继承吧

  • 数组能够调用的函数有那些?

  • PWA使用过吗?serviceWorker的使用原理是啥?

  • ES6 之前使用 prototype 实现继承

  • 箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

  • 事件循环机制 (Event Loop)

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值