此次模拟的是history模式
前置的知识:插件、slot 插槽、混入、render 函数、运行时和完整版的 Vue
History模式
- 通过history.pushState()方法改变地址栏,并把当前地址记录到浏览器的访问历史中
- 监听popstate事件,可以监听到浏览器历史操作的变化
- 根据当前路由地址找到对应组件重新渲染
Vue Router 模拟实现分析
- 回顾 Vue Router 的核心代码
// router/index.js
// 注册插件
// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
routes: [
{ name: 'home', path: '/', component: homeComponent }
]})
// main.js
// 创建 Vue 实例,注册 router 对象
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 类图
- install静态方法
let _Vue = null
class VueRouter {
static install(Vue){
//1 判断当前插件是否被安装
if(VueRouter.install.installed){
return;
}
VueRouter.install.installed = true
//2 把Vue的构造函数记录在全局变量中(我们在VueRouter的一些实例方法中会使用到,比如创建router-link和router-view组件的时候的时候会用到Vue.component())
_Vue = Vue
//3 把创建Vue的实例传入的router对象注入到Vue实例(我们以后会使用到this.$router就是此时注入到Vue实例)
// 我们要把router注入到所有的Vue实例上,我们所有的组件也都是Vue实例,我们想让所有的实例共享一个成员,所以我们把它设置到Vue构造函数的原型上
// 给所有的Vue实例混入一个选项,在这个选项里面我们设置一个beforeCreate,此时我们才可以获取到Vue实例,才能通过this.$options.router获取到创建Vue的实例传入的router对象
_Vue.mixin({
beforeCreate(){
// 防止执行很多次,创建组件的时候就不执行了,创建Vue实例的执行
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
- 构造函数
options存储配置routeMap存储options中传入的routes(路由规则)解析出来后的值=> { 路由地址: 路由组件 },将来在路由地址变化的时候在此找到对应的组件,并渲染到浏览器中data当路由变化的时候要自动加载组件,所以data需要是响应式的,我们可以借助Vue.observable()创建响应式的对象
constructor(options){
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current:"/" // 存储当前的路由地址
})
this.init()
}
- init()方法
init(){
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
- createRouteMap方法 作用:将options中传入的routes(路由规则)转换成键值对的形式=> { 路由地址: 路由组件 },存储到routeMap中
createRouteMap(){
//遍历所有的路由规则把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
- Vue的构建版本
运行时版:不支持template模板,需要打包的时候提前编译
完整版:包含运行时和编译器,体积比运行时版本大10k左右,程序运行的时候会把模板转换成
vue-cli创建的项目默认的是运行时版本的Vue
render函数
- initComponent()方法 我们会在此创建router-link 和 router-view
initComponent(Vue){
Vue.component("router-link",{
props:{
to:String
},
render(h){
return h("a",{
attrs:{
href:this.to // 我们实现的时history模式所以没有拼接#号
},
on:{
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
// pushState({},"标题",地址)
history.pushState({},"",this.to) // 可以改变地址栏的路径,但不会向服务器发送请求,还会把这次路径记录到历史中
// 把当前的路径存储到data.current中,因为这个data是响应式的,当data改变会重新加载对应的组件并渲染到视图中
this.$router.data.current=this.to
e.preventDefault() // 阻止a标签的默认行为
}
}
// template:"<a :href='to'><slot></slot><>"
})
const self = this
Vue.component("router-view",{
// 找到当前的路由地址,然后在routeMap找到对应的组件
render(h){
// self.data.current
const cm=self.routeMap[self.data.current]
return h(cm)
}
})
}
- initEvent()方法 作用:浏览器点击前进后退的时候重新渲染组件
initEvent(){
window.addEventListener("popstate",()=>{
this.data.current = window.location.pathname
})
}