模拟 vue-router hash 模式的基础功能实现
模拟 vue-router 的实现
以下内容基于 vue-router hash 模式来实现
实现 vue-router 的核心步骤
- 构建 vue-router 类
- 构建响应式数据属性 current,动态渲染 dom
- 添加静态方法 install,完成 Vue 模块的注册
- 初始化 router-link,router-view 组件,负责路由跳转和组件渲染
- 通过 match 函数匹配路由,找到需要渲染的组件
- 监听 hash 的变化,动态改变 current 属性
构建类
class MiniRouter {
constructor(options) {
// 存储参数
this.options = options
// 存储路由
this.routes = options.routes
// 初始化函数
this.init()
}
init() {
// 所有初始化操作放在这里
}
}
构建响应式数据属性 current
为了可以在路由发生变化的时候去更新视图,我们可以实例化一个响应式对象 data,并给定 响应式属性 current 来记录当前的 hash,当 current 发生变化的时候,vue 就会动态更新视图
class MiniRouter {
constructor(options) {
...
// 创建一个响应式的属性 current,当 current 改变的时候就会重新渲染 dom
this.data = $Vue?.observable({
// 给定初始 hash
current: window.location.hash.replace(/#/, '')
})
}
}
install方法
当使用 Vue.use(MiniRouter) 的时候,他要求传入的对象,必须存在一个 install 方法,它会自动帮我们执行这个方法,并将 Vue 构造函数传入该方法。所以我们可以在 install 方法中编写一些初始化代码。
而且因为 MiniRouter 是一个类,所以 install 方法应该是一个静态方法
let $Vue = null
let installed = false
class MiniRouter {
// install 方法,是用于 vue 来注册该组件的必备方法
static install(Vue) {
// 判断当前插件是否被安装
if (installed) {
return
}
// 这里我使用局部变量来缓存模块安装判断
installed = true
// 局部保存 Vue 构造函数,将来会用到
$Vue = Vue
// console.log('route installed')
// 当 vue 实例化之后,将 router 实例注入到 vue 实例中
$Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// 将 router 实例保存到 Vue 的 prototype 上
$Vue.prototype.$router = this.$options.router
}
}
})
}
...
}
初始化 router-link,router-view 组件
router-link 组件是用于改变当前url hash 值和改变属性 current 值的组件
router-view 组件则是负责在 hash 发生变化后,将匹配到的路由组件渲染到页面上
class MiniRouter {
...
initComponents() {
// 初始化一个 route-link 组件
$Vue.component('router-link', {
// 接收 to 参数,指定跳转连接
props: {
to: String
},
// 渲染 dom
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
// 指定组件的点击事件
clickHandler(e) {
// 通过 push State 改变 hash
history.pushState({}, '', `#${this.to}`)
// 改变 current 值,动态渲染视图
this.$router.data.current = this.to
// 阻止默认事件跳转
e.preventDefault()
}
}
})
const self = this
// 初始化一个 router-view 组件,进行组件渲染
$Vue.component('router-view', {
render(h) {
// 匹配路由对象,找到需要渲染的组件
let cm = self.match(self.data.current)?.component
return h(cm)
}
})
}
}
initComponents 方法需要在 init 中执行
match 匹配路由
match 函数可以说是这里最核心的方法,它可以根据传入的 path,找到与之匹配的路由对象和部分参数。
class MiniRouter {
...
match(path) {
// 存储匹配到的路由及部分参数
let matchRoute = null
// 遍历所有的路由,知道匹配到对应的路由
this.routes.every(route => {
// 通过 getRouteType 判断当前 route 的类型
let routeType = this.getRouteType(route.path)
// 找到 route 匹配的规则,用于判断当前路由和传入的 path 是否匹配
let routeMethod = this[`match${routeType}Route`]
// 如果规则不存在,则跳过
if (!routeMethod) {
return true
}
// 判断路由和 path 是否匹配,并返回对应的数据参数
let matchResult = routeMethod(path, route)
if (matchResult) {
// 匹配成功,储存返回的参数,并跳出循环
matchRoute = matchResult
return false
}
// 进行下一次循环
return true
})
// 返回匹配结果,若没有匹配成功则返回 null
return matchRoute
}
}
match 函数内部还有其他的方法,这里不一一阐述,有兴趣可以看源码。核心就是通过 getRouteType 找到路由的类型(静态路由,动态路由,统配符路由),然后找到对应类型下的路由匹配规则this[match${routeType}Route],进行匹配和返回。
监听 hash 改变
最后,我们需要通过监听 popState 来监听 hash 的改变,从而动态改变 视图,这一步主要是监听浏览器的前进,后退,或者用户直接改变hash的操作
class MiniRouter {
...
initEvent() {
document.addEventListener('popstate', () => {
this.data.current = window.location.hash.replace(/#/, '')
})
}
}
initEvent 方法需要在 init 中执行
以上内容仅供学习参考,如需完整代码,可以私聊我

856

被折叠的 条评论
为什么被折叠?



