Vue 前置知识
- 插件
- 混入
- Vue.observable()
- 插槽
- render 函数
- 运行时和完整版的Vue
Hash 模式
- URL中# 后面的内容作为路径地址**(改变#后面的内容,浏览器不会向服务器请求,但是会把地址记录到浏览器的访问历史中)**
- 监听hashchange 事件
- 根据当前路由地址找到对应组件重新渲染
History 模式
- 通过history.pushState() 方法改变地址栏**(pushState 方法只是改变地址栏,并把地址记录到浏览器的访问历史中,并不会向服务器发送请求)**
- 监听popstate 事件(back,forward 或者前进后退按钮时会触发, pushState 和replaceState方法不会触发)
- 根据当前路由地址找到对应组件重新渲染
Vue Router的模拟实现(History模式)
回顾代码使用:
// 通过插件引入,Vue.use 方法中可以传入函数或对象,
// 如果传入函数会直接调用这个函数,如果传入对象,Vue.use内部会调用这个对象中的install方法
// VueRouter 是个对象,所以我们需要实现一个install方法
Vue.use(VueRouter)
// 接下来创建实例(所以应该是一个构造函数或者是个类)
// 这里VueRouter 是个类,并且类中有个静态的install
// Vue Router 的参数需要可以传入个路由规则
const router = new VueRouter({
routes: [
{name: 'home', path: '/', component: homeComponent}
]
})
// 创建Vue 实例, 注册router对象
new Vue({
router,
render: h=> h(App)
}).$mount('#app')
VueRouter -- install
Vue.use 调用install方法时会传递两个参数,一个是Vue构造函数 ,另一个是可选的选项对象
let _Vue = null
class VueRouter {
// 这里我们不需要可选的选项对象
static install(Vue) {
// 1、判断当前插件是否已经被安装,如果已经被安装了就不需要重复安装
if(VueRouter.install.installed){
return;
}
Vue.install.installed = true
// 2、把Vue 构造函数记录到全局变量中,因为当前install是静态方法,在VueRouter实例方法时需要使用
_Vue = Vue
// 3、把创建Vue 实例时候传入的router对象注入到所有的Vue 实例上
// _Vue.prototype.$router = this.$options.router(但是直接写,的this 是VueRouter)
// 混入
_Vue.mixin({
beforeCreate(){
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
VueRouter -- 构造函数
class VueRouter {
constructor(options){
this.potions = options
// 路由规则解析成键值对 地址:组件
this.routeMap = {}
// 响应式对象,地址改变,对应改变组件
this.data = ——Vue.observable({
current: '/'
})
}
}
VueRouter createRouterMap
这个方法的作用把路由规则转换成键值对的形式,在地址发生变化时,可以很容易找到组件
class VueRouter {
createRouteMap(){
// 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap 中
this.options.routes.forEach(router => {
this.routeMap[route.path] = route.component
})
}
}
VueRouter router-link
先看使用方式如下:
<router-link to="/">Home</router-link>
实现方式如下:
initComponents(Vue){
// 创建组件
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>' // 创建插槽
})
}
调用如下
// 添加一个init 方法
init(){
this.createRouteMap()
this.initComponents(_Vue)
}
并在install 的可访问到的实例中访问他
static install(Vue){
_Vue.mixin({
beforeCreate(){
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
Vue 构建版本
- 运行时版: 不支持template模板, 需要打包时提前编译(使用render 函数创建虚拟dom ,并将它渲染到试图)
- 完整版: 包含运行时和编译器(作用是运行时把模板转换成render函数),体积比运行时版大10K左右
vue cli 默认使用的是运行时版本的vue , 所以上面的代码编写方式会报错(单文件组件中也会写template,但是不会报错的原因是,会将其预编译成render 函数)
解决方法:
方法一、使用完整版的Vue, 将vue cli 切换到完整版本
\\ 在vue.config.js
module.exports = {
runtimeCompiler: true // 开启编译器
}
方法二、还是使用运行时的Vue, 将上述代码中template改成render 函数
initComponents(Vue){
// 创建组件
Vue.component('router-link', {
props: {
to: String
},
render (h){
return h('a', {
attrs: {
href: this.to
}
},[this.$slot.default]}
}
// template: '<a :href="to"><slot></slot></a>' // 创建插槽
})
}
VueRouter router-view
initComponents(Vue){
...router-link
// 创建组件
const self = this;
Vue.component('router-view', {
props: {
to: String
},
render (h){
// 当前路由地址self.data.current
const component= self.routeMap[self.data.current]// self.data 是响应式对象,因此路径改变时会引发组件的改变
// 也就是如果发生组件不改变的bug,有可能时这里的这个对象已经被改变不是响应式了
return h(component}
}
// template: '<a :href="to"><slot></slot></a>' // 创建插槽
})
}
pushState
上述代码直接to 是会刷新浏览器的,但是我们不需要刷新浏览器,因此需要用到pushSate, 修改router-link
initComponents(Vue){
// 创建组件
Vue.component('router-link', {
props: {
to: String
},
render (h){
return h('a', {
attrs: {
href: this.to
},
on: {
click:this.clickHandler
}
},[this.$slot.default]}
},
methods: {
clickHandler(e){
// 三个参数(data, title, )第一个参数是之后调用popState 时传递给popState 的事件参数
// title 网页标题
// 超链接跳转的地址
history.pushState({}, '', this.to)
this.$router.data.current = this.to // 响应式对象,会重新加载对应的组件
e.preventDefault()
}
}
// template: '<a :href="to"><slot></slot></a>' // 创建插槽
})
}
vueRouter 回退 popState(Hash 模式监听hashChange)
发现目前点击前进和后退的时候,地址栏改变了,但是组件的内容并没有改变,因为没有代码触发current 改变
window.addEventListener('popstate', ()=>{
this.data.current = window.location.pathname
})