vue-router
Vue Router 是 Vue.js 官⽅的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单页面应用变得易如反掌。
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
安装: vue add router
核⼼步骤:
步骤⼀:使⽤vue-router插件,router.js
import Router from 'vue-router'
Vue.use(Router)
步骤二: 声明一个路由表(是一个映射表,path和组件是一个映射关系)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
步骤三:创建Router实例,router.js(然后导出)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
步骤四: 在根组件上添加该实例,main.js
import router from './router'
new Vue({
router,
}).$mount("#app")
步骤五: 添加路由视图,App.vue
<router-view></router-view>
导航链接
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
以上大概是vue-router的基本使用,下面进入简版源码实现部分
vue-router源码实现
需求分析
- spa 页面不能刷新
- hash方式 例如: #/home
- 或者History api 例如: /about
- 根据url显示对应的内容
- router-view
- 数据响应式: current变量持有url地址,一旦变化,动态重新执行render
任务
- 实现一个插件(创建VueRouter类和install⽅法)
- 实现VueRouter类
- 处理路由选项
- 监控url的变化
- 相应变化
- 实现一个install 方法
- $router的注册
- 两个全局组件
- 实现VueRouter类
创建kvue-router.js
let Vue; // 引⽤构造函数,VueRouter中要使⽤
// 保存选项
class VueRouter {
constructor(options) {
this.$options = options;
}
}
// 插件:实现install⽅法,注册$router
VueRouter.install = function(_Vue) {
// 引⽤构造函数,VueRouter中要使⽤
Vue = _Vue;
// 任务1:挂载$router
Vue.mixin({
beforeCreate() {
// 只有根组件拥有router选项
if (this.$options.router) {
// vm.$router
Vue.prototype.$router = this.$options.router;
}
}
});
// 任务2:实现两个全局组件router-link和router-view
Vue.component('router-link', Link)
Vue.component('router-view', View)
};
export default VueRouter;
为什么要⽤混⼊⽅式写?主要原因是use代码在前,Router实例创建在后,⽽install逻辑⼜需要⽤ 到该实例,这里是一个比较巧妙的方法(主要是为了延迟执行)
创建router-view和router-link
创建krouter-link.js
export default {
// 定义传入的参数
props: {
to: {
type: String,
require: true
}
},
render(h) {
// <router-link to="/home"/>
// <a href="#/home">XXX</a>
// 通用性更好
return h('a',{
attrs: {
href: '#' + this.to
}
}, this.$slots.default)
// 需要当前环境支持jsx
// return <a href={'#' + this.to}> {this.$slots.default}</a>
}
}
创建krouter-view.js
export default {
render(h) {
// 暂时先不渲染任何内容
return h(null);
}
}
监控url变化
定义响应式的current属性,监听hashchange事件
class VueRouter {
constructor(options) {
// current应该是响应式的
Vue.util.defineReactive(this, 'current', '/')
// 定义响应式的属性current
const initial = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'current', initial)
// 监听hashchange事件
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1)
}
}
动态获取对应组件,krouter-view.js
export default {
render(h) {
// 动态获取对应组件
let component = null;
this.$router.$options.routes.forEach(route => {
if (route.path === this.$router.current) {
component = route.component
}
});
return h(component);
}
}
提前处理路由表
提前处理路由表避免每次都循环
class VueRouter {
constructor(options) {
// 缓存path和route映射关系
this.routeMap = {}
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route
});
}
}
使⽤,krouter-view.js
export default {
render(h) {
const {routeMap, current} = this.$router
const component = routeMap[current] ? routeMap[current].component : null;
return h(component);
}
}
下面附上完整代码
// 实现一个插件
// 1. 返回一个函数
// 2. 或者返回一个对象,它有一个install方法
let _Vue = null
/**
* @class VueRouter
* @param options 选项
*/
class VueRouter {
constructor(options) {
// options 配置选项: router - 路由表
this.$options = options
// 缓存path 和route的映射关系
this.routeMap = {}
// 找到当前url对于的组件
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route
})
// 需要定义一个响应式的current属性
const initial = window.location.hash.slice(1) || '/'
// defineReactive 给一个对象定义响应式数据
_Vue.util.defineReactive(this, 'current', initial)
// 监控url的变化
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange () {
this.current = window.location.hash.slice(1)
console.log(this.current)
}
}
VueRouter.install = function(Vue) {
// 引用Vue构造函数,在上面的VueRouter中使用
_Vue= Vue
// 1. 挂载$router
// 利用混入,延迟执行
Vue.mixin({
// 此处的this 指的是vue根实例
beforeCreate() {
if(this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 2. 定义两个全局组件router-link, router-view
Vue.component('router-link', {
// 定义传入的参数
props: {
to: {
type: String,
require: true
}
},
render(h) {
// <router-link to="/home"/>
// <a href="#/home">XXX</a>
// 通用性更好
return h('a',{
attrs: {
href: '#' + this.to
}
}, this.$slots.default)
// 需要当前环境支持jsx
// return <a href={'#' + this.to}> {this.$slots.default}</a>
}
})
Vue.component('router-view', {
render(h) {
// 找到当前的url对应的组件
const { routeMap, current } = this.$router
const component = routeMap[current] ? routeMap[current].component : null
return h(component)
}
})
}
export default VueRouter