1.路由模式
vue-router 的路由模式有三种: hash、history、abstract;
- hash:浏览器环境,使用 URL hash 值来做路由;支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
- history:依赖 HTML5 History API 和 服务器配置;
- abstract:支持所有 javaScript 运行环境,如 node.js 服务器;如果发现没有浏览器的 API,路由会自动强制进入这个模式; 源码逻辑
switch (mode) {
case 'history' :
this.history = new HTML5HIstory(this, option.base)
break
case 'hash' :
this.history = new HashHistory(this, option.base, this.fallback)
break
case 'abstract' :
this.history = new AbstractHistory(this, options.base)
break
default :
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode : ${mode}`)
}
}
2.如何使用
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
// 2.声明一个路由表
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
// 3.创建一个Router实例
const router = new VueRouter({
routes
})
export default router
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
//views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<router-view></router-view>
</div>
</template>
//views/Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
//App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
3.代码实现
- 定义插件(有install方法) 和 类
- router-link和router-view组件标签
- 实现监听url变化#hash响应,这里只实现hash模式
- 实现路由嵌套
1. 定义插件 和 VueRouter类
//1 定义一个可以实例化的类
class VueRouter {
}
// 定义插件 需要有一个install的静态方法
VueRouter.install = function(Vue) {
}
export default VueRouter
2. 定义两个组件标签 router-link和router-view
let _Vue //记录一个全局的vue对象 通过install时候传入,有效分离router组件与vue的关系
VueRouter.install = function(Vue) {
// 引用Vue构造函数,在上面VueRouter中使用
_Vue = Vue
// 1.挂载$router
Vue.mixin({
beforeCreate() {
// 此处this指的是组件实例
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 2.定义两个全局组件router-link,router-view
Vue.component('router-link', {
// template: '<a>'
props: {
to: {
type: String,
require: true
},
},
render(h) {
// <router-link to="/about">
// <a href="#/about">xxx</a>
// return <a href={'#'+this.to}>{this.$slots.default}</a>
return h('a', {
attrs: {
href: '#' + this.to
}
}, this.$slots.default)
}
})
Vue.component('router-view', {
render(h) {
// 找到当前url对应的组件
const {routeMap, current} = this.$router
const component = routeMap[current] ? routeMap[current].component : null
// 渲染传入组件
return h(component)
}
})
}
3. 实现监听url变化#hash响应
通过变量值+vue实现界面响应式
class VueRouter {
// 选项:routes - 路由表
constructor(options) {
this.$options = options
// 缓存path和route映射关系
this.routeMap = {}
this.$options.routes.forEach(
route => {
this.routeMap[route.path] = route
})
// 需要定义一个响应式的current属性
const initial = window.location.hash.slice(1) || '/'
_Vue.util.defineReactive(this, 'current', initial)
// 监控url变化
window.addEventListener('hashchange', this.onHashChange.bind(this))
//如果要实现history模式 ,可以监听popstate实现
}
onHashChange() {
// 只要#后面部分
this.current = window.location.hash.slice(1)
console.log(this.current);
}
}
4. 实现路由嵌套
///router/index.js
import Vue from 'vue'
import VueRouter from './vue-router'
import Home from '../views/Home.vue'
import Info from '../views/Info.vue'
Vue.use(VueRouter)
const routes = [
{
path : '/',
name : "Home",
component : Home
},
{
path : '/about',
name : "About",
component : () => import('../views/About.vue'),
children : [
{
path : '/about/info',
name : "Info",
component : Info
},
]
},
]
const router = new VueRouter(routes)
export default router
//新增views/Info.vue
<template>
<div class="info">
<h1>This is an about/info page</h1>
</div>
</template>
//独立组件文件 router-link.js 和 router-view.js
//router-link.js
export default {
props: {
to: String
},
render(h){
return h("a",{
attrs: {
href : "#" + this.to
}
},
this.$slots.default
)
}
}
//router-view.js
export default {
render(h){
//新增属性 isRouterView 用于标记自己是isRouterView,注意是父组件 先设置isRouterView=true,再到子组件
//用于子组件遍历向上遍历是是判断父级是否为isRouterView
this.$vnode.data.isRouterView = true
let depth = 0 //默认从根往上找,如 /About/Info/Detail,那么就是从 Detail -> Info -> About,结果depth = 2
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if(vnodeData) {
if(vnodeData.isRouterView) {
depth++
}
}
parent = parent.$parent
}
let component = null
console.log("matchList",this.$router.matchList , "depth" ,depth)
const route = this.$router.matchList[depth]
if (route) {
component = route.component
}
return h(component)
}
}
//vue-router.js
import Vue from 'vue'
import RouterLink from './router-link'
import RouterView from './router-view'
let _Vue
class VueRouter {
constructor(options) {
this.current = ''
this.$options = options
this.routeMap = {}
this.$options .forEach(obj => {
this.routeMap[obj.path] = obj
});
this.current = window.location.hash.slice(1) || '/'
this.matchList = []//响应改成matchList
Vue.util.defineReactive(this,'matchList',[])
this.match()
this.onHashChange = this.onHashChange.bind(this)
window.addEventListener('hashchange',this.onHashChange)
}
onHashChange() {
this.current = window.location.hash.slice(1)
this.matchList = []
this.match()
}
//约束 嵌套router-view
match(routes) {
routes = routes || this.$options
for (const route of routes) { //遍历的是第一层
if (route.path === '/' && this.current === '/') { //判断如果是根 则直接返回
this.matchList.push(route)
return
}
//如果当前是 /about ,
if (route.path !== '/' && this.current.indexOf(route.path) != -1) {
this.matchList.push(route) //添加一个/about
if(route.children) {
this.match(route.children) //再递归 添加 children 里的 /about/info
}
}
}
}
}
VueRouter.install = function (Vue) {
_Vue = Vue
Vue.mixin({
beforeCreate(){
if(this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
Vue.component("router-link", RouterLink)
Vue.component("router-view", RouterView)
}
export default VueRouter
4.测试代码
http://localhost:8080/#/about/info
- matchList 为解析的vue的list集合
- depth 为索引
- 第一个 router-view 是在 App.vue,解析的时候在 matchList 长度2 , depth = 0 ,matchList[0]为about元素,所以解析出地址为 http://localhost:8080/#/about/ ,把内容挂载到App.vue上。
- 第二个 router-view 是在 About.vue,解析的时候在 matchList 长度2 , depth = 1 ,matchList[1]为info元素,所以解析出地址为 http://localhost:8080/#/about/info ,把内容挂载到About.vue上。