再次我们不需要过多的操作,只需要声明 3 个属性
-
this.options = options
记录构造函数中传入的选项 options
-
this.routeMap = {}
把 options 中传入的 routes 也就是路由规则解析出来,键值对的形式,键---路由地址,值---路由组件
-
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 模板,需要打包的时候提前编译
解决方法有两种
- 修改 vue.config.js 配置使用完整版的 vue
完整包含运行时和编译器,体积比运行时版大 10k 左右,程序运行的时候把模板转换成 render 函数
module.exports = {
// 渲染完整版 vue
runtimeCompiler: true
}
- 使用 render 函数
运行时版本的 vue 不带编译器,所以不支持组件中的 template 选项,编译器的作用就是把 template 编译成 render 函数,运行时的组件可以直接写 render 函数
单文件组件时一直使用 template 没写 render 是因为在打包的过程中把单文件的 template 编译成 render 函数了,这叫做预编译
render
-
render 函数接受一个参数,通常叫做 h 作用是帮我们创建虚拟 DOM,h 由 vue 传递
-
h 函数接受 3 个参数
-
创建这个元素的选择器。
-
给标签设置一些属性,如果是 DOM 对象的属性,需要添加到 attrs 中。
-
生成的元素的子元素,所以是数组形式
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 个 方法,这个方法有三个作用
-
通过 pushState 方法 改变浏览器地址栏,但不给服务器发请求
-
将当前路由路径 同步给默认值
-
阻止标签默认事件
pushState 方法接受 3 个参数
- data。
- title 网页的标题。
- 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)