Vue Router 是 Vue.js 官方的路由管理器,可以让构建单页面应用变得易如反掌
单页面应用(Single Page Application,SPA)
应用只有一个完整的页面,它在加载页面时,不会加载全部页面,只加载一个首页的容器,其它页面的内容在首页容器某个位置,需要页面切换时只更新某个指定的容器中内容
- 单页面应用的核心之一是:更新视图而不重新请求页面
单页面应用不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容
但当url变化,都会造成页面的刷新,目前有两种方式实现:
- 一种方式是用hash实现路由
- 另一种方式是用history
Vue-Router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式
Vue-Router的使用
通过Vue-cli安装Vue项目时可以把Vue-Router一起安装到项目中,或者另外安装引入
Vue-Router 使用步骤
1、安装依赖
yarn add vue-router --dev
2、在Vue中使用 注册路由组件
import Vue from 'vue'
import VueRouter from 'vue-router'
// Vue.use 是用来注册插件 会调用传入对象的install方法
Vue.use(VueRouter)
3、定义路由规则 路由规则是一个数组
const routes = [
{
path: '/', // 路径
name: 'Home', // 路由名称 路径和名称在push和replace中都可以作为跳转目标
component: Home // 对应的组件
}
]
4、创建路由对象
const router = new VueRouter({ router // ES6的语法 需要传入一个属性router的数组})
5、在Vue实例创建时传入创建路由实例
new Vue({ router, render: h => h(App)}).$mount('#app')
new Vue 时传入的router是为了给Vue实例注入 route 这两个属性
- $route 存储当前的路由规则信息,路径、参数等信息
- $router 是提供一些操作路径的方法 push、replace、go 还能通过currentRoute拿到当前路由规则信息
6、在首页(主页面)使用显示路由组件,和使用显示跳转(不是必须,可以用编程式导航代替)
// app.vue
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/info">Info</router-link>
</div>
<!-- 这里会被具体的路由组件内容替换掉 -->
<router-view/>
</div>
动态路由
在路由规则里 path后面跟上 :/参数名,就可以在url上传参数给路由对应的组件
还可以再路由规则中指定props: true,指定后url上面的参数会作为props传给组件
{
path: '/detail/:id', // :id 即一个占位符 通过这个占位来匹配变化的位置
name: 'Detail',
props: true, // 开启了props为true后,url上面的参数会作为props传给组件
// 即路由懒加载 仅当访问此路由地址时才会加载对应组件 提高性能
component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')
}
// Detail.vue
// 通过$route来获取参数
{{ $route.params.id }}
// 或者因为在路由规则开启了props:true,所以在props中接收参数 页面直接使用
{{ id }}
export default {
props: ['id']
}
嵌套路由
当多个路由的组件都有相同的内容,可以将这些相同内容提取到公共的组件当中,在公共组件里面通过路由的方式(使用)显示不同的路由组件
{ path: '/',
component: Layout,
children: [
// '/'会显示 Layout Index
{
name: 'index',
path: '',
component: Index
},
// '/detail/20' 会显示 Layout detail
{
name: 'detail',
path: 'detail/:id',
props: true,
component: () => import('../view/detail.vue')
}
]
}
// Layout.vue
<div>
<div>Layout</div> <div>
<!-- '/'会显示 Layout Index -->
<!-- '/detail/20' 会显示 Layout detail -->
<router-view></router-view>
</div>
</div>
编程式导航
除了用外,更多情况下是使用$router的方法来实现路由跳转
-
push 方法 会记录本次历史
-
replace 方法 不会记录本次历史 会替换历史记录
-
go 方法 会以0为基准跳转到相对页面
// 跳转 会记录本次历史 this.router.push({ name: 'Home' }) this.router.replace('/login') // 按照当前历史进行跳转 负数是回退 正数是向前 this.$router.go(-2)
Hash模式和History模式 区别
两种方式都是,当路径法发生变化,不会向服务器发送请求,使用js监视路径的变化根据不同的地址渲染不同的内容
Hash模式
- url里面有#,#后面是路由地址
- 原理实现是基于锚点 和 onhashchange事件
History模式
- url里面没有#
- 原理实现是基于HTML5中History API
- history.pushState()
- history.replaceState()
History模式 服务端支持
History需要服务器支持,因为History没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,把除了静态资源外所有路由都重定向到根页面
在node中,可以使用 connect-history-api-fallback 来重定向到根页面
const history = require('connect-history-api-fallback')
// 使用
app.use(history)
在nginx中,在配置文件中进行修改
// 对应的conf文件
location / {
root html;
index index.html index.htm;
// 访问路径 有路径返回路径 没有就返回index.html
try_files $uri $uri /index.html;
}
一个简单Vue-Router实现
Vue-Router中有两种模式
Hash 模式
- URL 中 # 后面的内容作为路径地址
- 监听 hashchange 事件
- 根据当前路由地址找到对应组件重新渲染
History 模式
- 通过 history.pushState() 方法改变地址栏
- 监听 popstate 事件
- 根据当前路由地址找到对应组件重新渲染
使用自定义的Vue-Router
需要先在Vue中使用自定义的Vue-Router,注入到Vue实例中
router/index.js
import Vue from 'vue'
// 导入自定义的Vue-Router
import VueRouter from '../vuerouter'
// 使用
Vue.use(VueRouter)
// 路由规则
const routes = [...]
// 创建实例
const router = new VueRouter({
mode: 'history', // 指定模式
routes // 传入路由规则
})
自定义的VueRouer需要实现的功能
- Vue.use()会调用传入对象的静态install方法,需要在这里实现注入到Vue中,保存Vue实例给其他地方用
- install里面mixin混合时可以在Vue实例beforeCreate生命周期中,可以进行一些初始化init()
- constructor 构造器中要把传入的options保存起来
- 初始化init() 把路由规则数组转成对象方便获取;初始化组件;监听浏览器的历史前进后退事件
- 编码方式的路由跳转功能
install()
// 用_Vue记录Vue实例
let _Vue = null
// vue.use(VueRouter) 会调用这个方法
static install (Vue) {
// 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 把vue构造函数记录到全局变量
_Vue = Vue
// 混入
_Vue.mixin({
beforeCreate () {
// vue实例才有options.router
if (this.$options.router) {
// 把router注入到vue实例
_Vue.prototype.$router = this.$options.router
// 执行一下init
this.$options.router.init()
}
}
})
}
constructor()
constructor (options) {
// 保存一下 options mode
this.options = options
this.routeMap = {}
this.mode = this.options.mode || 'hash'
// 构造器会晚于install的执行
// install里面会给_Vue赋值为Vue实例
// 这里给data创建为响应式数据 数据改变就重新渲染
this.data = _Vue.observable({
current: ''
})
}
init()
// 把路由规则routes转成一个routeMap 键值对
this.createRouteMap()
// 初始化 <router-view> 和 <router-link> 组件
this.initComponents(_Vue)
// 监听后退 前进
this.initEvent()
// 有第一个规则 就默认显示第一个
if (this.options.routes[0]) {
this.routerPush(this.options.routes[0].path)
}
}
createRouteMap()
createRouteMap () {
// 遍历所有路由规则 把路由规则解析成键值对 存储到routeMap
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents()
需要用编码的方式创建,这个方法用render函数来实现
initComponents (Vue) {
const self = this
Vue.component('router-link', {
props: {
to: String
},
render (h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
// 默认插槽
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
self.routerPush(this.to)
e.preventDefault() // 阻止事件默认行为
}
}
})
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent()
两种模式对应了两个window的变化事件
initEvent () {
if (this.mode === 'hash') {
window.addEventListener('hashchange', () => {
this.data.current = location.hash.substr(1)
})
} else if (this.mode === 'history') {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
}
routerPush()
页面跳转
routerPush (to) {
// 这里的this是指向VueRouter创建的实例
if (this.mode === 'hash') {
// 修改hash值
window.location.hash = to
// this.data === _Vue.prototype.$router.data
this.data.current = to
} else if (this.mode === 'history') {
// 改变浏览器地址栏 pushiState 不向服务器发送请求
history.pushState({}, '', to)
this.data.current = to
}
}