前端路由的核心:改变视图的同时不会向后端发出请求。
简述:
前端路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的 Hash 值发生了变化
③ 前端路由监听了到 Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
总结:前端路由,指的是 Hash 地址与组件之间的对应关系!
目录结构
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router' //引用路由
import store from './store'
// 引入通用scss文件初始化样式
import '@/assets/styles/index.scss'
// 导入vant css
import 'vant/lib/index.css';
import 'vant/lib/icon/local.css'
// 适应屏幕宽度
import '@/assets/js/adapt.js'
// 引入全局组件
import '@/assets/js/global.js'
Vue.config.productionTip = false
router.beforeEach((to,from,next)=>{//路由导航钩子 - 对登录拦截
if(to.meta.title){
document.title=to.meta.title
}
next();
})
new Vue({
router,//挂载
store,
render: h => h(App)
}).$mount('#app')
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error)
}
const routes = [
{
path: '/register',
name: 'register',
component: ()=>import(/*webpackChunkName:"register"*/ "../views/recomA/register.vue"),
meta: {
title: '注册'
}
},
// 重定向规则
{ path: '/', redirect: '/home' },
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{
path: '/',
name: 'register',
component: ()=>import(/*webpackChunkName:"register"*/ "../views/recomA/register.vue"),
meta: {
title: '注册'
}
},
{
path: '/login',
name: 'login',
component: ()=>import(/*webpackChunkName:"register"*/ "../views/recomA/login.vue"),
meta: {
title: '登录'
}
},
{
path: '/activity',
name: 'activity',
component: ()=>import(/*webpackChunkName:"activity"*/"../views/recomA/activity.vue"),
meta: {
title: '活动'
}
},
{
path: '/updateInfo',
name: 'updateInfo',
component: ()=>import(/*webpackChunkName:"updateInfo"*/ "../views/recomA/updateInfo.vue"),
meta: {
title: '用户信息更新'
}
},
{
path: '/check',
name: 'check',
component: ()=>import(/*webpackChunkName:"check"*/ "../views/recomA/check.vue"),
meta: {
title: '推荐记录查询'
}
},
];
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
export default router
views/activity.vue
<template>
<div class="banner">
<Swipper :imgList="imgList"></Swipper>
<van-button type="primary" size="small" @click="updateInfo">注册信息更新</van-button>
<div v-for="(banner,index) in Banner" :key="index" class="bannerImg" @click="gotoMenu(banner.bannerId)">
<van-image :src="banner.bannerPath"/>
<van-cell :title="banner.title" is-link arrow-direction="down"/>
</div>
<van-button class='RecomRecord' @click='RecomRecord' type="primary" block>推荐记录查询</van-button>
</div>
</template>
<script>
// 引入样式
import 'vue-easytable/libs/themes-base/index.css'
import Vue from 'vue';
import {Button, Lazyload, Cell, Image, Dialog} from 'vant';
import {VTable, VPagination} from 'vue-easytable';
Vue.use(Button).use(Lazyload).use(Cell).use(Image).use(VTable).use(VPagination).use(Dialog);
import Swipper from '@/components/recomA/swiper'
import img from '@/assets/images/recomA/banner.png'
import img1 from '@/assets/images/recomA/activity-zjgm.png'
import {ActivityBanner} from '@/api/index'
import {smpGetActId} from '@/api/index'
import wxSign from '@/utils/wxSign'
export default {
name: "activity",
components: {
Swipper
},
data() {
return {
imgList: [
{img: img}
],
activeKey: 0,
list: [],
Banner: []
}
},
mounted() {
wxSign.getwxShareToken()
this.getActivityBanner()
},
methods: {
updateInfo() {
this.$router.push({
name: 'updateInfo',
path: 'updateInfo',
query: {
'rKey': this.$route.query.rKey,
'actId': 1,
'supplierId': ''
}
})
},
RecomRecord() {
this.$router.push({
name: 'check',
path: 'check',
query: {
'rKey': this.$route.query.rKey,
'actId': 1,
'supplierId': ''
}
})
},
gotoMenu(actId) {
if (actId == '1') {
this.$router.push({
name: 'recommall',
path: 'recommall',
//actId=1¶m=1&supplierId=
query: {
'rKey': this.$route.query.rKey,
'actId': 1,
'supplierId': ''
}
})
}
},
getActivityBanner() {
this.Banner.push({'bannerId': "1", 'bannerPath': img1, 'title': '员工直接推荐购买'})
}
}
}
</script>
vue.config.js
module.exports = { // 配置 webpack-dev-server 行为
devServer:{
host:"127.0.0.1",// 设置IP
port:8088,// 设置端口号
open:true// 是否自动打开
}
}
页面视图
路由之间跳转?
声明式(标签跳转)
‹router-link :to=‘index’›
编程式( js跳转)
router.push(‘index’) 跳转可以通过地址跳转还可以通过name跳转this.$router.push("地址") this.$router.replace("地址") this.$router.back this.$router.go(1) this.$router.go(-1)
拿上面视图activity.vue来说(代码在上面全都有,下面详细使用说明)
- 点击
注册信息更新
时,跳转到updateInfo.vue
页面时做的跳转
实现前端路由的两种方式
vue-router通过hash与History两种方式实现前端路由,
更新视图但不重新请求页面
是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式:hash模式
利用URL中的hash(“#”),它的特点在于:hash 虽然出现URL中,但不会
被包含在HTTP请求中
,对后端完全没有影响,因此改变hash不会重新加载页面
。
hash 也 称作 锚点,本身是用来做页面定位的,她可以使对应 id 的元素显示在可视区域内。由于 hash 值变化不会导致浏览器向服务器发出请求
,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制
,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。已经有 hash 模式了,而且 hash 能兼容到IE8, history 只能兼容到 IE10,为什么还要搞个 history 呢?
- 首先,hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,
hash 的传参是基于 url 的
,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中
history模式
利用了HTML5 history中新增加的pushState() 和 replaceState() 方法
在vue-router中,它提供mode参数来决定采用哪一种方式,选择流程如下:
mode 参数:
- 默认hash
- history 注:如果浏览器不支持history新特性,则采用hash方式
History
接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。它提供了一些属性和方法:History.back() //history.go(-1) History.forward() //history.go(1) History.go() History.pushState():按指定的名称和URL(如果提供该参数)将数据push进会话历史栈 History.replaceState():按指定的数据,名称和URL(如果提供该参数),更新历史栈上最新的入口
Vue2.0中 r o u t e r 和 router和 router和route的区别
router是一个全局的对象,我们可以通过 $router.push() 向history栈中添加一个路由,也可以通过 $router.replace() 替换路由,没有历史记录
route是一个跳转的路由对象,可以获取对应的name,path,params,query传递参数
this.$route 是路由的“参数对象”
1、在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值。
2、 // 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
// 接收 props 数据
props: ['mid'],
//页面显示数据
{{mid}}
- 注意1:在 hash 地址中, / 后面的参数项,叫做“路径参数”
在路由“参数对象”中,需要使用this.$route.params
来访问路径参数- 注意2:在 hash 地址中,? 后面的参数项,叫做“查询参数”
在路由“参数对象”中,需要使用this.$route.query
来访问查询参数- 注意3:在 this.$route 中,path 只是路径部分;fullPath 是完整的地址
this.$router 是路由的“导航对象”
(详细见导航部分)
导航
声明式导航 & 编程式导航
- 在浏览器中,点击链接实现导航的方式,叫做声明式导航。
例如:
普通网页中点击<a>
链接、vue 项目中点击<router-link>
都属于声明式导航- 在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。
例如:
普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航- vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
① this.$router.push('hash 地址')
跳转到指定 hash 地址,并增加一条历史记录
② this.$router.replace('hash 地址')
跳转到指定的 hash 地址,并替换掉当前的历史记录
③ this.$router.go(数值 n)
实现导航历史前进、后退
push 和 replace 的区别:
push 会增加一条历史记录
replace 不会增加历史记录,而是替换掉当前的历史记录router.go 的简化用法:
在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法:
① $router.back()
在历史记录中,后退到上一个页面
② $router.forward()
在历史记录中,前进到下一个页面
Vue Router的导航守卫
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function (to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
- 全局解析守卫:router.beforeResolve 注册一个全局守卫。
这和router.beforeEach
类似,区别是在导航被确认之前,
同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。- 全局后置钩子:afterEach钩子不会接受
next
函数也不会改变导航本身- 路由独享守卫:
beforeEnter
守卫- 组件内的守卫:
beforeRouteEnter 、beforeRouteUpdate
(2.2 新增)beforeRouteLeave
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
路由缓存
- 路由缓存一:缓存全部路由
<keep-alive>
<router-view></router-view>
</keep-alive>
- 路由缓存二:指定路由缓存
<keep-alive include="该路由的name名称">
<router-view></router-view>
</keep-alive>
- 路由缓存三:存在多个路由,想缓存部分路由
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
{
path:'/test',
name:'Test',
component: Test,
meta: {keepAlive: true} //true缓存 false不缓存
}
需在app中设置缓存的页面,还需在router.js里设置缓存的路由
从缓存页面跳转到不缓存页面,或者从不缓存页面跳转到缓存页面的时候,会发现watch是不能监听路由的,是因为缓存和不缓存页面分别在不同的div里面,一个div里面是不可能监听到另一个div的路由的,所有需要把监听的路由都加上缓存
(在路由添加 meta: { keepAlive: true })
,路由在缓存页面之间进行跳转的时候,就可以通过监听路由来进行判断数据是否需要更新。
vue keep-alive 缓存后, 进入缓存页需要再次更新
beforeRouteEnter (to, from, next) {
next (vm => {
vm.getData()
})
},
mounted: function () {
this.getData()
}
keep-alive 数据更新问题
在项目中使用<keep-alive>包含<router-view>
实现页面缓存,加速页面加载,
同时,这种方式带来一些弊端
当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。
这就带来一个问题,之前在项目中使用mounted在页面加载时获取数据,使用后方法不再生效,
根据上面的解释,将mounted替换为activated即可。
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view> //当前进入的路由 meta里面 keepAlive为true时走这里
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view> //当前进入的路由 meta里面 keepAlive为false时走这里 下面 if 判断进行了取反处理
{
meta: {
requireAuth: true,
keepAlive: true, //当前的.vue需要缓存
},
path: "/Smartbrand",
name: "Smartbrand",
component: () => import("../components/smartbrand.vue"),
},
路由懒加载:
懒加载:就是当你需要的时候才会进行加载,按需加载。
单页面应用的问题就是所有的页面都需要引入一个js文件,wepback打包的时候生成一个js,这个js会在所有组件切换的时候调用。
首页引入了所有的一个js,日志也会引入所有的一个js,这样首页加载速度变慢。
如何让首页只引入首页的js,日志页面只引入日志页面的js? 解决方法就是路由懒加载
像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,
造成进入首页时,需要加载的内容过多,时间过长,会出现先长时间的白屏,
即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,
需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
进入首页不用一次加载过多资源造成用时过长!!!
component: resolve=>require(["@/page/index"],resolve)
component:()=>import("@/page/log"),
function resolveView(view){
return ()=>import(`@/page/${view}`)
}
工程化上的按需加载,说白了就是异步组件。
vue里面的vue-router生态里面,实现路由的懒加载的时候,通过
new VueRouter({
routes:[
{component:()=>import("XXX"),path:"/home"}
]
})
1.vue中的路由采用了异步组件+webpack的代码分割功能实现了路由的懒加载,减少首页的加载用时
2.react里面实现懒加载的话,怎么做?
通过使用react-loadable的第三方模块,实现懒加载
拓展 - vue+axios实现登录拦截(路由拦截/http拦截)
详细请查看:
https://www.cnblogs.com/guoxianglei/p/7084506.html
遇到的问题
Vue中history路由刷新404解决
vue hash模式下,URL中存在'#',用'history'模式就能解决这个问题。
但是history模式会出现刷新页面后,页面出现404。解决的办法是用nginx配置一下。
在nginx的配置文件中修改
方法一:
location /{
root /data/nginx/html;
index index.html index.htm;
if (!-e $request_filename) {
rewrite ^/(.*) /index.html last;
break;
}
}
方法二:
server {
listen 8081;#默认端口是80,如果端口没被占用可以不用修改
server_name myapp.com;
root D:/vue/my_app/dist;#vue项目的打包后的dist
location / {
try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在nginx中刷新出现404
index index.html index.htm;
}
#对应上面的@router,主要原因是路由的路径资源并不是一个真实的路径,所以无法找到具体的文件
#因此需要rewrite到index.html中,然后交给路由在处理请求资源
location @router {
rewrite ^.*$ /index.html last;
}
#.......其他部分省略
}
子路由的值必须是数组, 不然报错router.children.some is not a function
baseRoute: {
path: '/baseManager',
component: Layout,
redirect: 'noredirect',
name: 'baseManager',
authority: 'baseManager',
meta: {
title: '基础配置管理',
icon: 'setting'
},
children: [
{
path: 'userManager',
component: _import('permission/admin/user/index'),
name: 'userManager',
authority: 'userManager',
meta: {
title: '用户管理',
icon: 'fa-user'
}
},
{
path: 'menuManager',
component: _import('permission/admin/menu/index'),
name: 'menuManager',
authority: 'menuManager',
meta: {
title: '菜单管理',
icon: 'category'
}
},
{
path: 'groupManager',
component: _import('permission/admin/group/index'),
name: 'groupManager',
authority: 'groupManager',
meta: {
title: '角色权限管理',
icon: 'group_fill'
}
}
]
},
跳转页面,页面数据刷新路由不变化
这种情况使用$route
无法获取到当前页面路由传递的参数
解决方案:通过window.location.href获取当前的url进行拼接跳转
this.$router.replace({path: "/pinPage?"+this.GetQueryString('type')});
}
微信支付路由层级问题
通过this.$router.replace({ name: "topin17Day",params:{id:that.$route.query.id} })
跳转路由,使路由层级超过一层将无法调起支付
解决方案:改为query拼接,或使用?拼接
this.$router.replace({path: "/pinPage?"+this.GetQueryString('type')});
that.$router.replace({ path: '/pinPage',query:{id:gid,type:1} });