Hash与History模式的区别
大致区别:
- Hash模式是基于锚点,以及onhashchange事件
- Hash模式会先判断当前浏览器是否支持window.pushState()方法,否则通过window.location
- History模式是基于HTML5中的History API
当调用history.push方法的时候会向服务器发送请求- history.pushState() IE10以后才支持 ,不会向服务器发送请求,只会记录路径,等于是在客户端完成的,不涉及服务端
- history.replaceState()
History模式的使用
- history需要服务器的支持
- 单页应用中,服务器不存在http://test.xxxx.com/login这样的地址会返回找不到该页面
- 在服务器端除了静态资源外都返回单页应用的index.html(Vue-cli自带的服务器已经配置好了)
配置完了的服务器,当刷新http://test.xxxx.com/login时,服务器会返回index.html内容,index.html会判断login下面的内容,于是展现页面
自实现vue-router
要想自己实现vue-router之前,先了解一下vue-router的大致流程
// router/index.js
//注册插件
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')
VueRouter类所包含内容↓
options: 传递给VueRouter的router对象
data:data中有一个current属性对应当前的路由地址(因此data是响应式的对象)
routeMap:路由地址和组件的对应关系,路由规则解析到routeMap中
install():VueRouter的静态方法(不对外暴露),用来实现插件机制
Constructor():构造函数,初始化上述三个属性
initEvent():用来注册pushState()方法,用来监听浏览器历时的变换
createRouteMap():用来初始化routeMap属性,把传入的构造规则转换成键值对的形式,键就是路由地址,值就是对应组件,存储在routeMap中
initComponents(Vue):用来创建router-link跟router-view这两个组件
init():调用createRouteMap(),initEvent(),initComponents(Vue)
具体实现
install()方法:
- 判断当前插件已经被安装
- 把Vue构造函数记录到全局变量中,因为install是静态方法,但是在VueRouter的实例中还会用到Vue构造函数
- 把创建Vue实例时传入的router对象注入到所有的Vue实例上
static install (Vue) {
//1. 判断当前插件已经被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2. 把Vue构造函数记录到全局变量中,因为install是静态方法,但是在VueRouter的实例中还会用到Vue构造函数
_Vue = Vue
// 3. 把创建Vue实例时传入的router对象注入到所有的Vue实例上
// 混入, 在beforeCreate钩子中获取Vue实例时传入的router
_Vue.mixin({
beforeCreate () {
// 如果是vue实例则将router注入,如果是组件则不注入,不然会注入好多次重复的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
构造函数
constructor (options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: '/' //用来存储当前的路由地址
})
}
createRouteMap
createRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents
方法1:
由于vue-cli默认是不带编译器版本的,也就是说不支持template选项,所以要创建vue.config.js,然后在这个文件开启runtimeCompiler配置。详细参考官方文档
initComponents (Vue) {
Vue.component('route-link', {
props: {
to: String
},
template: '<a :href="to"><slot></solt></a>'
})
}
方法2:
使用运行时版本的vue,组件会通过render函数进行一个虚拟dom的转换,所以实现render就能编译组件
initComponents (Vue) {
Vue.component('route-link', {
props: {
to: String
},
render (h) {
return h('a', {
attrs: {
href: this.to
}
}, [this.$slots.default])
}
})
}
这里采用的是方法2
initComponents (Vue) {
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) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent
当浏览器点击返回、前进按钮的时候,页面并没有加载组件
initEvent () {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
完整代码参考这里