vuerouter简单实现
路由
hash 和 history
-
hash 模式基于锚点以及 onhashchange 事件
-
history 模式基于 html5 中的 history API
使用 history.pushState()和 history.replaceState()实现 但是 IE10 以后才支持
相较于 push 和 replace 两个方法 pushState 和 replaceState 不会向服务器请求,实现了客户端路由
node 中的 history
因为 vueCli 中的 devServer 默认配置支持了 histroy 路由 所以需要到 node 或 nginx 中查看具体的配置
在 node 中 不使用 history 支持的中间件时 在访问路由时会返回默认的 get error 页面
开启 history 中间件支持后 可以正常跳转
node 中的 history
nginx 中也会跳出 404 页面
nginx.conf 中 修改
http 下的 server 下的
location / {
root html;
index index.html index.htm;
# 新添加
try_files $uri $uri/ index.html;
}
vueRouter 实现
-
hash
- hash 地址可以使用 location.url 切换 url 地址 如果只更改了#后的地址 浏览器不会请求服务器这个地址
- hashchange 事件 找到对应路径的组件 重新渲染
-
history
- historyAPI 用来改变浏览器 url 地址 同样不会向服务器请求
- 监听 popState 事件 可以获取到浏览器地址的变化 (只能获取到前进或后退按钮 而 pushState 和 replaceState 触发的地址改变不会监听到 可以监听到 history.back 和 forward)
- 找到对应地址的组件重新渲染
-
实现分析
-
前提:Vue.use() 传入函数时会直接调用 传入对象会调用对象的 install()
-
new VueRouter({routes: Array<path>}) : router
-
类图
- 类
- VueRouter
- 属性
- +options 记录构造器中传入的 options
- +data 有 current 属性 记录当前路由地址 响应式对象(使用 Vue.Observable())
- +routeMap 记录地址和组件的对应关系
- 方法
- +constructor(options):VueRouter
- _install(Vue):void
- +init():void
- +initEvent():void 注册 popState 事件
- +createRouterMap():void
- +initComponent():void 创建 router-view 和 router-link 这两个组件
- 类
-
-
实现
let _Vue;
export default class VueRouter {
static isInstalled = false;
static install (Vue) {
// use还可以传递options 这里没有做
// 判断当前插件是否已经被安装
if (this.isInstalled) {
return new Error("VueRouter has been installed!!");
}
// 需要保存Vue 因为之后还要创建router-view 和router-link这两个组件 全局注册组件component静态方法
_Vue = Vue;
// 因为所有的vue实例都有$router 需要将router注册到Vue实例中
// _Vue.prototype.$router = this.$options.router 此时获取不到Vue实例
// 利用混入特性 将router注入到Vue中
// 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
_Vue.mixin({
beforeCreate () {
// 在beforeCreate钩子函数调用时 同时将router添加到Vue的原型上
// 因为beforeCreate钩子函数在子组件实例化时也会执行 此时需要判断 组件则不执行添加操作
// 创建Vue实例时 会传入(render,router,store等) 而组件实例化时则大部分什么都没有
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router;
}
},
});
this.isInstalled = true;
}
options;
routeMap;
data;
constructor(options) {
this.options = options;
this.routeMap = {};
this.data = _Vue.observable({
// 为防止刷新页面时进入错误路由 这里直接获取到访问的路由进行初始化
current: window.location.pathname,
});
this.init();
}
init () {
this.createRouteMap();
this.initComponent(_Vue);
this.initEvent();
}
createRouteMap () {
// 遍历路由 存储到routemap中
this.options.routes.forEach((route) => {
this.routeMap[route.path] = route.component;
});
}
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String,
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to;
e.preventDefault();
}
},
// 因为当前打包后使用的是运行时版本的vue 不能使用template 可以使用完整版vue (vueconfig 属性runtimecompiler 为true)
// 如果想使用运行时版本
// template: `<a :href="to"><slot></slot></a>`
render (h) {
return h('a', {
attrs: {
href: this.to,
},
on: {
click: this.clickHandler,
}
}, [this.$slots.default])
// 因为插槽没有起名字 所以使用默认插槽
},
});
const self = this;
Vue.component('router-view', {
props: {
to: String,
},
// 因为当前打包后使用的是运行时版本的vue 不能使用template 可以使用完整版vue (vueconfig 属性runtimecompiler 为true)
// 如果想使用运行时版本
// template: `<a :href="to"><slot></slot></a>`
render (h) {
const path = self.data.current;
let component = self.routeMap[path];
if (!component) component = self.routeMap['*']
return h(component)
// 因为插槽没有起名字 所以使用默认插槽
},
});
}
initEvent () {
// 发现当点击浏览器的前进后退时不会改变画面 刷新时对应组件异常 所以要监听这些事件来改变视图
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname;
})
}
}
- 实现关键问题
- 在使用混入对Vue原型操作时 判断这个beforeCreated是谁触发的 如果是组件等触发的不可以执行
必须是有router这个配置项的祖先vm实例初始化调用beforeCreated才行 否则之后所有的vue实例都没有$router这个属性 - 在router创建实例时 执行init 获取到routes并保存
- 当前实现只是简单的非嵌套路由,如果嵌套路由需要对routes进行处理
- error(404)画面需要在匹配组件失败后 使用404画面替换掉router-view
- 响应式对象实现的data对象 和 h函数是实现router-view的主要
- 。。。待补充
- 在使用混入对Vue原型操作时 判断这个beforeCreated是谁触发的 如果是组件等触发的不可以执行