一、Vue Router 使用步骤
- 创建路由对象
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'
// 1. 注册路由插件
Vue.use(VueRouter)
// 路由规则
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/blog',
name: 'Blog',
component: () => import('../views/Blog.vue')
},
{
path: '/photo',
name: 'Photo',
component: () => import('../views/Photo.vue')
}
]
// 2. 创建 router 对象
const router = new VueRouter({
routes
})
export default router
- 注册 router 对象
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
// 3. 注册 router 对象
router,
render: h => h(App)
}).$mount('#app')
// 在创建 Vue 实例的时候,注入了 router 选项,在生成的 Vue 实例中就会有 $route、$router 两个属性
// $route 存放了路由规则,$router 就是路由对象,提供了很多路由方法
- 在页面或组件中使用,创建占位符、链接
<template>
<div id="app">
<div>
<img src="@/assets/logo.png" alt="">
</div>
<div id="nav">
<!-- 5. 创建链接 -->
<router-link to="/">Index</router-link> |
<router-link to="/blog">Blog</router-link> |
<router-link to="/photo">Photo</router-link>
</div>
<!-- 4. 创建路由组建的占位 -->
<router-view/>
</div>
</template>
二、动态路由使用
动态路由指的是带参数的路由,路径参数用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来,让组件来接收路由的参数;同时也可以开启路由的 props ,会把 URL 中的参数传递给组件,组件可以通过 props 来接收 URL 参数
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/detail/:id', // :id 是一个占位符
name: 'Detail',
props: true, // 开启 props,会把 URL 中的参数传递给组件,组件可以通过 props 来接收 URL 参数
component: () => import('../views/Detail.vue') // 路由懒加载,用户访问时候才加载
}
]
const router = new VueRouter({
routes
})
export default router
// Detail.vue
<template>
<div>
<!-- 方式1: 通过当前路由规则,获取数据 -->
通过当前路由规则获取:{{ $route.params.id }}
<br>
<!-- 方式2:路由规则中开启 props 传参 -->
通过开启 props 获取:{{ id }}
</div>
</template>
<script>
export default {
name: 'Detail',
props: ['id'] // 接收参数 id
}
</script>
<style>
</style>
三、嵌套路由使用
因为首页和详情页都有相同的头部和尾部,这个时候就可以使用嵌套路由,头部和尾部都提取到了 Layout 组件中,嵌套路由会把外部 path 和 children 中的 path 进行合并,然后会先加载外部 Layout 组件,最后加载首页组件或详情页组件
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 加载组件
import Layout from '@/components/Layout.vue'
import Index from '@/views/Index.vue'
import Login from '@/views/Login.vue'
Vue.use(VueRouter)
const routes = [
{
name: 'login',
path: '/login',
component: Login
},
// 嵌套路由
{
path: '/',
component: Layout,
children: [
{
name: 'index',
path: '',
component: Index
},
{
name: 'detail',
path: 'detail/:id',
props: true,
component: () => import('@/views/Detail.vue')
}
]
}
]
const router = new VueRouter({
routes
})
export default router
Layout 中会使用 router-view 占位符,来展示首页或者详情页
// components/Layout.vue
<template>
<div>
<div>
<img width="25%" src="@/assets/logo.png">
</div>
<div>
<router-view></router-view>
</div>
<div>
Footer
</div>
</div>
</template>
<script>
export default {
name: 'layout'
}
</script>
<style scoped>
</style>
四、编程式导航
除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现
- this.$router.push
这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL,该方法的参数可以是一个字符串路径,或者一个描述地址的对象
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
-
this.router.push,唯一不同的是,它在导航时不会向 history 添加新记录
-
this.$router.go 这个方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步
五、Hash 模式和 History 模式的区别
Hash 模式和 History 模式都是客户端路由实现方式,路径发生变化的时候,不会向服务端发送请求,而是用 javaScript 监视路径的变化,根据不同的地址渲染不同的内容,如果需要服务端内容的话,需要发送 ajax 请求来获取
- 表现形式的区别
-
Hash 模式的路径带#号,#号后面是路由地址,可以通过问号携带 url 参数,路径中带着和数据无关的符号,比如:music.163.com/#/playlist?…
-
History 模式的路径就是正常的路径,比如music.163.com/playlist/31…
- 原理的区别
-
Hash 模式是基于锚点,以及 onhashchange 事件,通过锚点的值作为路由地址,当地址变化后出触发 onhashchange 事件,根据路径决定页面上呈现的内容。Vue Router 默认使用的是 hash 模式,使用 hash 来模拟一个完整的 URL,通过 onhashchange 监听路径的变化
-
History 模式是基于 HTML5 中的 History API ,也就是 history.pushState()和 history.replaceState()这两个方法,pushState 和 push 方法的区别是,当我们调用 history.push 方法的时候,路径会发生变化,会向服务器发送请求,而 history.pushState 不会向服务器发送请求,只会改变浏览器地址栏的地址,并且把地址记录到历史记录当中,所以通过 history.pushState 可以实现客户端路由,都是 history.pushState 是 ie10 以后才支持的,所以有浏览器兼容问题
Version:0.9 StartHTML:0000000105 EndHTML:0000000475 StartFragment:0000000141 EndFragment:0000000435
六、HTML5 History 模式的使用
首先需要将返回的 router 对象设置为 History 模式
const router = new VueRouter({
mode: 'history',
routes
})
-
首先要明白的是,在 history 模式中页面的跳转是不会向服务器发送请求,只会改变浏览器地址栏的地址,但是当浏览器刷新的时候,浏览器就会自动向服务器发送请求,所以说,History 模式是需要服务器的支持 !!!
-
所以在服务端应该除了静态资源外都返回单页应用的 index.html,在 node 服务端中可以使用 history 插件来实现
const path = require('path')
// 导入处理 history 模式的模块
const history = require('connect-history-api-fallback')
// 导入 express
const express = require('express')
const app = express()
// 注册处理 history 模式的中间件
app.use(history())
// 处理静态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))
// 开启服务器,端口是 3000
app.listen(3000, () => {
console.log('服务器开启,端口:3000')
})
七、VueRouter 模拟实现( history 模式为例)
先看 Vue Router 的核心代码
// 1. 注册路由插件
Vue.use(VueRouter) // 内部调用传入对象的 install 方法
// 路由规则
const routes = [
{
path: '/',
name: 'Index',
component: Index
}
]
// 2. 创建 router 对象
const router = new VueRouter({
routes
})
export default router
Vue.use 这个方法可以传入函数或者对象,如果传入函数的话,Vue.use 会直接调用这个函数,如果传入对象的话,Vue.use 会直接调用这个对象的 install 方法,因为这里传入的是一个对象,所以要去实现 VueRouter 的 install 方法,因为后续要使用 new 关键字来创建实例对象,所以可以得知 VueRouter 是一个类,并且这个类有一个静态的 install 方法
7-1、VueRouter install 实现
- Vue.use 调用 install 方法的时候会传递两个参数,一个是 Vue 构造函数,第二个是可选的选项对象
- install 方法的的三个作用
- 判断当前插件是否被安装
- 把 Vue 的构造函数记录在全局
- 把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
7-2、VueRouter 构造函数实现
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
// options 中的路由规则解析后都存储在 routeMap 中
// routeMap 的键是路由地址,值是路由组件
this.routeMap = {}
// data 是一个响应式的对象,里面有一个属性 current 来记录当前路由地址
this.data = _Vue.observable({
current: "/"
})
}
}
7-3、VueRouter createRouteMap 方法实现
createRouteMap 方法的作用是将构造函数传过来的 options 中的路由规则转化成键值对的形式存储到 routeMap 对象中
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
// options 中的路由规则解析后都存储在 routeMap 中
// routeMap 的键是路由地址,值是路由组件
this.routeMap = {}
// data 是一个响应式的对象,里面有一个属性 current 来记录当前路由地址
this.data = _Vue.observable({
current: "/"
})
this.init()
}
init() {
this.createRouteMap()
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
}
7-4、VueRouter router-link 实现
initComponent 方法的作用是创建两个组件,分别是 router-link 和 router-view,router-link 这个组件最终要渲染成超链接
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
// options 中的路由规则解析后都存储在 routeMap 中
// routeMap 的键是路由地址,值是路由组件
this.routeMap = {}
// data 是一个响应式的对象,里面有一个属性 current 来记录当前路由地址
this.data = _Vue.observable({
current: "/"
})
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent(Vue) {
Vue.component("router-link", {
props: {
to: String
},
render(h) {
// h 函数的作用是创建虚拟dom
// 第一参数是标签,
// 第二个参数是属性和事件
// 第三个参数是元素的内容
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
}
})
}
}
7-5、VueRouter router-view 实现
router-view 组件相当于一个占位符,在 router-view 组件内部要获取到当前路由地址,获取到对应的组件并且渲染到 router-view 的位置中
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
// options 中的路由规则解析后都存储在 routeMap 中
// routeMap 的键是路由地址,值是路由组件
this.routeMap = {}
// data 是一个响应式的对象,里面有一个属性 current 来记录当前路由地址
this.data = _Vue.observable({
current: "/"
})
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent(Vue) {
Vue.component("router-link", {
props: {
to: String
},
render(h) {
// h 函数的作用是创建虚拟dom
// 第一参数是标签,
// 第二个参数是属性和事件
// 第三个参数是元素的内容
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
methods: {
clickhander(e) {
// history.pushState 仅仅是修改浏览器地址,不向服务器发送请求
history.pushState({}, "", this.to)
// 通过 data.current 记录当前地址
this.$router.data.current = this.to
// 阻止默认行为执行
e.preventDefault()
}
}
})
// 因为在 render 方法中,this 指向已经不是 VueRouter 实例了,所以要保存 this
const self = this
Vue.component("router-view", {
render(h) {
const cm = self.routeMap[self.data.current]
return h(cm)
}
})
}
}
7-6 VueRouter initEvent 实现
let _Vue = null
class VueRouter {
static install(Vue) {
// 1、判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2、把 Vue 的构造函数记录在全局变量
_Vue = Vue
// 3、把创建 Vue 的实例传入的 router 对象注入到所有的 Vue 实例上
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
// options 中的路由规则解析后都存储在 routeMap 中
// routeMap 的键是路由地址,值是路由组件
this.routeMap = {}
// data 是一个响应式的对象,里面有一个属性 current 来记录当前路由地址
this.data = _Vue.observable({
current: "/"
})
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent(Vue) {
Vue.component("router-link", {
props: {
to: String
},
render(h) {
// h 函数的作用是创建虚拟dom
// 第一参数是标签,
// 第二个参数是属性和事件
// 第三个参数是元素的内容
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
methods: {
clickhander(e) {
// history.pushState 仅仅是修改浏览器地址,不向服务器发送请求
history.pushState({}, "", this.to)
// 通过 data.current 记录当前地址
this.$router.data.current = this.to
// 阻止默认行为执行
e.preventDefault()
}
}
})
// 因为在 render 方法中,this 指向已经不是 VueRouter 实例了,所以要保存 this
const self = this
Vue.component("router-view", {
render(h) {
const cm = self.routeMap[self.data.current]
return h(cm)
}
})
}
initEvent() {
// 监听浏览器的历史的变化
window.addEventListener("popstate", () => {
this.data.current = window.location.pathname
})
}
}