Vue Router
后端路由与前端路由
后端路由:根据不同的URL地址分发不同的资源
前端路由:根据不同的用户事件,显示不同的页面内容(负责事件监听,触发事件后,通过事件函数渲染不同内容)
多页面应用模式MPA(Multi Page Application) | 单页面应用模式SPA(Single Page Application) | |
---|---|---|
应用构成 | 由多个完整页面构成 | 一个外壳页面和多个页面片段构成 |
跳转方式 | 页面之间的跳转是从一个页面到另一个页面 | 一个页面片段删除或隐藏,加载另一个页面片段并显示。片段间的模拟跳转,没有开壳页面 |
跳转后公共资源是否重新加载 | 是 | 否 |
URL模式 |
http://xxx/page1.html 和http://xxx/page2.html
|
http://xxx/shell.html#page1 和http://xxx/shell.html#page2
|
用户体验 | 页面间切换加载慢,不流畅,用户体验差,尤其在移动端 | 页面片段间切换快,用户体验好,包括移动设备 |
能否实现转场动画 | 否 | 容易实现(手机APP动效) |
页面间传递数据 | 依赖URL 、cookie 或者localstorage ,实现麻烦 |
页面传递数据容易(Vue 中的父子组件通讯props 对象或Vuex ) |
搜索引擎优化(SEO) | 可以直接做 | 需要单独方案(SSR) |
特别适用的范围 | 需要对搜索引擎友好的网站 | 对体验要求高,特别是移动应用 |
开发难度 | 较低,框架选择容易 | 较高,需要专门的框架来降低这种模式的开发难度 |
前端路由与SPA
传统的后端路由,根据客户端请求的不同网址,返回不同的网页内容。这样会造成服务器压力增加以及每次都重新请求,响应慢,用户体验下降。于是SPA应运而生,在url地址改变的过程中,通过js来实现不同的UI之间的切换,而不再向服务器重新请求页面,只通过ajax向服务器请求数据,对用户来说这种无刷新的、即时响应有更好的体验。其中根据url地址的变化而展示不同的UI,就是通过前端路由来实现的
前端路由的实现方式
基于location.hash实现(location.hash+hashchange事件)
location.hash的值是url中#
后面的内容,如http://www.163.com#netease
,location.hash为'#netease'
hash满足一下几个特性,才使得其可以实现前端路由:
-
url中hash值的变化并不会重新加载页面,因为hash是用来指导浏览器行为的,对服务端是无用的,所以不会包括在http请求中
-
hash值的改变,都会在浏览器的访问历史中增加一个记录,能通过浏览器的回退、前进控制hash的切换
-
我们可以通过onhashchange事件,监听到hash值的变化,从而响应不用路径的逻辑处理。我们就可以在onhashchange事件,根据hash转换来更新对应的视图,但不会去重新请求页面
window.addEventListener('hashchange', function() {}, false)
触发hash值的变化有2种方法:
一种是通过a标签,设置href属性,当a标签点击之后,地址栏会改变同时触发onhashchange事件
<a href="#kaola">to kaola</a>
另一种是通过js直接赋值给location.hash,也会改变url同时触发onhashchange事件
location.hash="#kaola"
具体实现方式:
<div id="app">
<a href="#/zhuye">主页</a>
<a href="#/keji">科技</a>
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>
<!-- component标签当做是组件的占位符 -->
<component :is="comName"></component>
</div>
<script>
const zhuye = {
template: "<h1>主页信息</h1>"
}
const keji = {
template: "<h1>科技信息</h1>"
}
const caijing = {
template: "<h1>财经信息</h1>"
}
const yule = {
template: "<h1>娱乐信息</h1>"
}
const vm = new Vue({
el: "#app",
data: {
comName: zhuye
},
// 注册私有组件
components: {
zhuye,
keji,
caijing,
yule
}
})
window.onhashchange = function () {
switch (location.hash.slice(1)) {
case "/zhuye":
vm.comName = zhuye
break
case "/keji":
vm.comName = keji
break
case "/caijing":
vm.comName = caijing
break
case "/yule":
vm.comName = yule
break
}
}
</script>
基于history新API实现(history.pushState()+popState事件)
history.pushState():会增加一条新的历史记录
传入的新url不一定是绝对地址。如果是相对地址,它将以当前url为基准。传入的新url与当前url应该是同源否则pushState()会抛出异常
const state = { 'page_id': 1, 'user_id': 5 } //一个与指定网址相关的状态对象
const title = null //新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
const url = 'hello-world.html' //新的网址,必须与当前url同源
window.history.pushState(state, title, url)
history.replaceState():会替换当前的历史记录
两个API相同之处:都可以修改url地址,操作浏览器的历史记录同时state会改变,但不会引起页面的刷新
通过onpopstate监听history.state
的变化,来指引js做加载以及渲染等任务
window.addEventListener('popstate',function(){
//获取到最新的state值
var state = history.state
})
hash模式 | history模式 | |
---|---|---|
url显示 | 有#,很Low | 无#,好看 |
回车刷新 | 可以加载到hash值对应页面 | 一般就是404掉了 |
支持版本 | 支持低版本浏览器和IE浏览器 | HTML5新推出的API |
history模式存在的问题:
export default new Router({
mode: 'history'
}
在路由跳转时,路径改变了,其实并没有加载页面,当我们刷新的时候就会报404错误,因为Vue Router设置的路径不是真实存在的路径
解决history模式下刷新报404的弊端,这就需要服务器端做点手脚,配置一下 apache 或 nginx 的url重定向,重定向到你的首页路由上就ok了(将不存在的路径请求重定向到入口文件index.html)
配置nginx的方式:
location / {
try_files $uri $uri/ /index.html
}
Vue Router插件
Vue Router是Vue.js官方的路由管理器,可以非常方便的用于SPA应用程序的开发,支持hash模式和HTML5 history模式
添加路由链接
router-link
标签默认被渲染为a标签
to
属性默认被渲染为href属性
to
属性值默认被渲染为#开头的hash地址
<router-link to="/user">User</router-link>
添加路由填充位(路由占位符)
将来通过路由规则匹配到的组件,将会被渲染到router-view
所在位置
<router-view></router-view>
router.js
每一个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性:
- path:表示当前路由规则匹配的hash地址
- component:表示当前路由规则对应要展示的组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from './components/Login.vue'
import Admin from './components/Admin.vue'
import Customer from './components/Customer.vue'
import AdminHome from './components/Admin/AdminHome.vue'
import CustomerHome from './components/Customer/CustomerHome.vue'
import UserList from './components/Admin/UserList.vue'
import MyOrder from './components/Customer/MyOrder.vue'
import HomeCooking from './components/Customer/HomeCooking.vue'
Vue.use(VueRouter)
// 创建路由实例对象router
const router = new VueRouter({
// routes是路由规则数组
routes: [
// redirect路由重定向,强制跳转
{path:'/', redirect:'/login'},
{path:'/login', component:Login},
{path:'/admin', component:Admin, redirect:'/adminHome', children:[
{path:'/adminHome', component:AdminHome},
{path:'/userList', component:UserList}
]},
{path:'/customer', component:Customer, redirect:'/customerHome', children:[
{path:'/customerHome', component:CustomerHome},
{path:'/myOrder', component:MyOrder},
{path:'/homeCooking', component:HomeCooking}
]}
]
})
// 挂载路由导航守卫
router.beforeEach((to, from, next) => {
// to将要访问的路径
// from代表从哪个路径跳转而来
// next是一个函数,表示放行
// next('/login')强制跳转
if(to.path === '/login') return next()
// 获取token
const token = window.sessionStorage.getItem('token')
if(!token) return next('/login')
next()
})
export default router
Vue Router动态匹配路由
通过动态路由参数的模式进行路由匹配:
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<router-view></router-view>
</div>
<script>
const User = {
// 路由组件中通过$route.params获取动态路由参数
template: "<h1>User组件--用户id为{{$route.params.id}}</h1>"
}
const Register = {
template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
routes: [{
path: "/",
redirect: "/user"
}, {
// 动态路由参数以冒号开头(:id)
path: "/user/:id",
component: User
},
{
path: "/register",
component: Register
}
]
})
const vm = new Vue({
el: "#app",
router: router
})
</script>
通过路由组件传递参数:
$route与对应路由形成高度耦合且不够灵活,所以可以使用 props 将组件和路由解耦
props的值为布尔类型:
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<router-view></router-view>
</div>
<script>
const User = {
// 使用props接收动态路由参数
props: ["id"],
template: "<h1>User组件--用户id为{{id}}</h1>"
}
const Register = {
template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
routes: [{
path: "/",
redirect: "/user"
}, {
path: "/user/:id",
component: User,
// props设置为true,route.params将会被设置为组件属性
props: true
},
{
path: "/register",
component: Register
}
]
})
const vm = new Vue({
el: "#app",
router: router
})
</script>
props的值为对象类型:
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<router-view></router-view>
</div>
<script>
const User = {
props: ["name", "age"],
template: "<h1>User组件--用户id为{{$route.params.id}}姓名为{{name}}年龄为{{age}}</h1>"
}
const Register = {
template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
routes: [{
path: "/",
redirect: "/user"
}, {
path: "/user/:id",
component: User,
props: {
name: "张三",
age: 20
}
},
{
path: "/register",
component: Register
}
]
})
const vm = new Vue({
el: "#app",
router: router
})
props的值为函数类型:
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<router-view></router-view>
</div>
<script>
const User = {
props: ["id", "name", "age"],
template: "<h1>User组件--用户id为{{id}}姓名为{{name}}年龄为{{age}}</h1>"
}
const Register = {
template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
routes: [{
path: "/",
redirect: "/user"
}, {
path: "/user/:id",
component: User,
props: route => ({
name: "张三",
age: 20,
id: route.params.id
})
},
{
path: "/register",
component: Register
}
]
})
const vm = new Vue({
el: "#app",
router: router
})
</script>
Vue Router命名路由
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link :to="{name: 'user', params: {id:3}}">User3</router-link>
<router-link to="/register">Register</router-link>
<router-view></router-view>
</div>
<script>
const User = {
props: ["id", "name", "age"],
template: "<h1>User组件--用户id为{{id}}姓名为{{name}}年龄为{{age}}</h1>"
}
const Register = {
template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
routes: [{
path: "/",
redirect: "/user"
}, {
// 命名路由
name: "user",
path: "/user/:id",
component: User,
props: route => ({
name: "张三",
age: 20,
id: route.params.id
})
},
{
path: "/register",
component: Register
}
]
})
const vm = new Vue({
el: "#app",
router: router
})
</script>
Vue Router编程式导航
页面导航的两种方式:
- 声明式导航:通过点击链接实现导航的方式,如网页中的
<a></a>
链接或者Vue中的<router-link></router-link>
- 编程式导航:通过调用JS形式的API实现导航的方式,如:网页中的
window.location.href
常用的编程式导航API:
- this.$router.push():跳转到指定url,向history栈添加一个新的记录,点击后退会返回到上个页面
- this.$router.replace():跳转到指定url,替换history栈中最后一个记录,点击后退会返回到上上个页面
- this.$router.go(n):向前或向后跳转n个页面,n可为正数也可为负数
this.$router.push()的参数规则:
-
字符串(路径名称):
this.$router.push('/home')
const User = { template: '<div><button @click="goRegister"></button></div>', methods: { goRegister: function(){ this.$router.push('/register') } } }
-
对象:
this.$router.push( {path:'/home'} )
或者this.$router.push( {name:'home'} )
-
命名路由(传递参数):
this.$router.push( {name:'/user', params:{userId:123}} )
-
带查询参数:
this.$router.push( {path:'/register', query:{name:'lisi'}} )
变成(/register?name=lisi
)
this.$router.go(n)的参数规则:
- this.$router.go(-1):后退一步记录
- this.$router.go(1):前进一步记录
- this.$router.go(3):前进三步记录
新闻动态跳转新闻动态详情页具体实现
http://www.zjyushi.com/news
http://www.zjyushi.com/newsDetail/1281104872833888258
在new.vue中点击跳转至对应的newsDetail
handleDetails (val) {
this.$router.push({
name: 'newsDetail',
// 匹配newsDetail命名路由并传递id参数
params: {
id: val.newsId
}
})
}
router.js
{
// 接收id参数
path: '/newsDetail/:id',
name: 'newsDetail',
component: resolve => require(['@/views/newsDetail'], resolve)
}
newsDetail.vue
created () {
// 路由组件中通过$route.params获取动态路由参数
this.newsId = this.$route.params.id
this.getNewsDetail()
},
methods: {
getNewsDetail () {
// axios携带id参数发起请求,查询出id对应的新闻动态
this.$apis.getNewsDetail({ newsId: this.newsId }).then(res => {
}).catch(() => {})
}
导航守卫
全局前置守卫
router.beforeEach((to, from, next) => {
// to将要访问的路径
// from代表从哪个路径跳转而来
// next是一个函数,表示放行
// next('/login')强制跳转
if(to.path === '/login') return next()
// 获取token
const token = window.sessionStorage.getItem('token')
if(!token) return next('/login')
next()
})
全局解析守卫
全局后置钩子
router.afterEach((to, from) => {
// ...
})
路由独享的守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
路由元信息(meta字段)
直接在路由配置的时候,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。用它来做登录校验再合适不过了
一个路由匹配到的所有路由记录会暴露为 $route
对象 (还有在导航守卫中的路由对象) 的 $route.matched
数组。因此我们需要遍历 $route.matched
来检查路由记录中的 meta
字段
只需要判断item下面的meta对象中的login_require是不是true,就可以做一些限制了
router.beforeEach((to, from, next) => {
if (to.matched.some(function (item) {
return item.meta.login_require
})) {
next('/login')
} else
next()
})
过渡效果
单个路由的过渡(每个路由组件有各自的过渡效果,可以在各路由组件内使用 <transition>
并设置不同的 name)
const Foo = {
template: `
<transition name="slide">
<div class="foo">...</div>
</transition>
`
}
const Bar = {
template: `
<transition name="fade">
<div class="bar">...</div>
</transition>
`
}
基于路由的动态过渡
<!-- 使用动态的 transition name -->
<transition :name="transitionName">
<router-view></router-view>
</transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
数据获取
有时候进入某个路由后,需要从服务器获取数据。如:在渲染用户信息时,需要从服务器获取用户的数据。可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航
滚动行为
使用keep-alive标签后部分安卓机返回缓存页位置不精确问题
解决方案:
meta:{
keepAlive:true //需要被缓存的组件
}
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
const router = new Router({
scrollBehavior(to, from, savedPosition) {
if (savedPosition && to.meta.keepAlive) {
return savedPosition
}
return { x: 0, y: 0 }
}
})
不能返回到原有浏览位置
当我们从A页面跳转到B页面,然后在B页面浏览一篇很长的文章,但是这时候我们讲页面滚动到某个位置的时候,我们不小心按了一下浏览器的后退键,这时候我们再按浏览器的前进键的时候会发现,该文章又要从头开始阅读了
在route后面加上scrollBehavior这个函数。该scrollBehavior函数接收to和from路由对象,第三个参数savedPosition仅在这是popstate导航(由浏览器的后退/前进按钮触发)时才可用该函数可以返回滚动位置对象。如果返回假值或空对象,则不会进行滚动
const router = new Router({
scrollBehavior(to, from, savedPosition) {
if (savedPosition && to.meta.keepAlive) {
return savedPosition
}
return { x: 0, y: 0 }
}
})
路由懒加载
当路由被访问的时候才加载对应组件,这样就更加高效
-
安装
@babel/plugin-syntax-dynamic-import
包 -
在 babel.config.js 配置文件中声明该插件
const prodPlugins = [] if(process.env.NODE_ENV === 'production'){ prodPlugins.push('transform-remove-console') } module.exports = { plugins: [ //发布阶段需要用到的插件数组 ...prodPlugins, '@babel/plugin-syntax-dynamic-import' ] }
-
将路由改为按需加载的形式
import Login from './components/Login.vue' import Admin from './components/Admin.vue' import Customer from './components/Customer.vue' import AdminHome from './components/Admin/AdminHome.vue' import CustomerHome from './components/Customer/CustomerHome.vue' // 改为按需加载的形式 const Login = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Login.vue') const Admin = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Admin.vue') const Customer = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Customer.vue') const AdminHome = () => import(/* webpackChunkName: "adminhome_customerhome" */ './components/Admin/AdminHome.vue') const CustomerHome = () => import(/* webpackChunkName: "adminhome_customerhome" */ './components/Customer/CustomerHome.vue')
Vue Router API
https://router.vuejs.org/zh/api/
路由对象
一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records),路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象
路由对象出现在多个地方:
-
在组件内,即
this.$route
-
在
$route
观察者回调内 -
router.match(location)
的返回值 -
导航守卫的参数:
router.beforeEach((to, from, next) => { // to和from都是路由对象 })
-
scrollBehavior
方法的参数:const router = new VueRouter({ scrollBehavior(to, from, savedPosition) { // to和from都是路由对象 } })
路由对象属性
-
$route.path
类型:
string
字符串,对应当前路由的路径,总是解析为绝对路径,如
"/foo/bar"
-
$route.params
类型:
Object
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象
-
$route.query
类型:
Object
一个 key/value 对象,表示 URL 查询参数。例如,对于路径
/foo?user=1
,则有$route.query.user == 1
,如果没有查询参数,则是个空对象 -
$route.hash
类型:
string
当前路由的 hash 值 (带
#
) ,如果没有 hash 值,则为空字符串 -
$route.fullPath
类型:
string
完成解析后的 URL,包含查询参数和 hash 的完整路径
-
$route.matched
类型:
Array<RouteRecord>
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是
routes
配置数组中的对象副本 (还有在children
数组)const router = new VueRouter({ routes: [ // 下面的对象就是路由记录 { path: '/foo', component: Foo, children: [ // 这也是个路由记录 { path: 'bar', component: Bar } ] } ] })
当 URL 为
/foo/bar
,$route.matched
将会是一个包含从上到下的所有对象 (副本) -
$route.name
当前路由的名称
-
$route.redirectedFrom
如果存在重定向,即为重定向来源的路由的名字
组件注入
注入的属性
通过在 Vue 根实例的 router 配置传入 router 实例,下面这些属性成员会被注入到每个子组件
- this.$router:router 实例
- this.$route:当前激活的路由对象。这个属性是只读的,里面的属性是 immutable (不可变) 的,不过你可以 watch (监测变化) 它
Vue使用watch监听路由的变化
watch除了可以监听data中数据的变化,还可以监听路由的变化。监听 this. r o u t e 或 t h i s . route或this. route或this.route.path 属性
watch:{
'$route.path': function(newVal,oldVal){
if(newVal === '/login'){
console.log('欢迎进入登录页面')
}else if(newVal === '/register'){
console.log('欢迎进入注册页面')
}
}
}
本文地址:https://blog.csdn.net/weixin_53430951/article/details/111132257