1. 概述
- 路由是一个网络工程里面的术语
- 路由就是通过
互联的网络
把信息从源地址传输到目的地址的活动 - 后端路由:后端处理URL和页面之间的映射关系
- 前端路由:前端处理URL和页面之间的映射关系
2. 后端路由阶段
-
早期的网站开发整个HTML页面是由服务器来渲染的。服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示
-
但是,一个网站,这么多页面,服务器如何处理呢?
●. 每个页面都有自己对应的网址,也就是URL
●. URL会发送到服务器,服务器通过正则对该URL进行匹配,并且最后交给一个Controller处理
●. Controller进行各种处理,最终生成HTML或者数据,返回给前端
●. 这就完成了一个IO操作 -
上面的这种操作就是后端路由,当我们页面中需要请求
不同的路径内容
时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端,这种情况下渲染好的的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化 -
后端路由的缺点:
●. 一种情况是整个页面的模块由后端人员来编写和维护
●. 另一种情况是前端开发人员如果要开发页面,需要通过PHP
和java
等语言来编写页面代码
●. 而且通常情况下HTML
代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情
3. 前端路由阶段
-
前后端分离阶段:
●. 随着Ajax的出现,有了前后端分离的开发模式
●. 后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过javaScript
将数据渲染到页面
●. 这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上
●. 并且当移动端(IOS/Android)
出现后,后端不需要进行任何处理,依然使用之前的一套API即可
●. 目前很多网站依然采用这种模式开发 -
单页面富应用(SPA)阶段:
●. 其实SPA最主要的特点就是在前端分离的基础上加了一层前端路由
●. 也就是前端来维护一套规则 -
前端路由的核心:
●. 改变URL,但是页面不进行整体的刷新
4. 认识vue-router
-
目前前端流行的三大框架,都有自己的路由实现
●. Angular 的ngRouter
●. React 的ReactRouter
●. Vue 的vue-router
-
vue-router 是
Vue.js
官方的路由插件,它和Vue.js
是深度集成的,适用于构建单页面应用vue-router
官网:https://router.vuejs.org/zh/ -
vue-router 是基于路由和组件的,路由用于设定访问路径,将路径和组件映射起来,在
vue-router
的单页面应用中,
页面的路径的改变就是组件的切换
5. 安装和使用vue-router
-
安装vue-router:
npm install vue-router --save // 这是运行时依赖
-
配置路由:
// 在 src/router/index.js 文件中 // 1. 导入路由模块: import VueRouter from 'vue-router' // 2. 导入Vue模块: import Vue from 'vue' // 3. 导入组件 import Home from '../components/Home.vue' // 这里的 .vue 后缀可以省略 import Home from '../components/Avout.vue' // 4. 安装路由插件: Vue.use(VueRouter) // 5. 创建路由实例,并且传入路由映射配置 const router = new VueRouter({ // 配置路由和组件之间的应用关系 routes:[ { // 网页重定向:当请求路径为空或者为为某些特点值,可以设置网页显示哪些东西。 path:'/', // path:'/' 和 path:'' 一个意思 redirect:'./home' }, { path:'/home', // 当文路径文 /home 的时候,显示下面的组件 component:Home }, { path:'/about', component:About } ] }) // 5. 导出路由对象: export default router
-
使用路由:
// 1. 在入口文件 main.js 中导入路由模块 import router from './router' // 会自动去 router 文件夹中找 index.js 文件 // 2. 在 Vue 实例中挂载创建的路由实例 new Vue({ el: '#app', router, render: h => h(App) }) //============================================================================ // 3. 使用路由:在 Vue 实例的的模板组件 App.vue 中,创建对应的组件标签 <div id="app"> <router-link to="/home">首页</router-link> // router-link 是 vue-router 插件内置的一个标签 <router-link to="/about">关于</router-link> // router-link 最终会被渲染成 a 标签 <router-view></router-view> // router-view 是占位标签,会被需要显示的页面替换掉 </div>
-
路径改变的模式:hash 与 history
// 1. 默认情况下路径改变都是通过hash值:http://localhost:8080/#/about // 2. 如果想改成history模式,路径的改变方式,在路由对象中添加一个属性mode const router = new VueRouter({ routes, mode:'history' }) // 注:改变之后的路径显示:http://localhost:8080/about
-
router-link 组件标签的属性
<router-link to="/home" tag='button'>首页</router-link> // 1. to='/home' 属性用于指定跳转路径 // 2. tag='button'属性可以指定标签渲染成什么组件,默认是a标签 // 3. replace 属性没有值,设置之后网页没有历史记录,也就是没有后退、前进 // 4. 处于活跃的 router-link 标签,会有一个默认类名: router-link-active // 可以在用此类名修改活跃标签的一些样式,活跃标签的默认类名也可以修改 // 修改当前router-link标签的活跃类名:给当前标签添加属性active-class='新类名' // 修改所有router-link标签的活跃类名:路由对象中添加一个属性linkActiveClass:'新类名'
-
如果不想用router-link 标签来跳转页面。也可以通过其他方式:
// 例如:把router-link标签换成button,然后监听button的点击事件, // 在script标签的导出对象中,添加组件方法methods methods:{ click () { this.$router.push('/home') } } // 注:每个组件中都会有一个路由对象$router,通过当前组件对象this调用 // 用phsh方法更改页面路径,或者replace方法也行 push() // 有历史记录 replace() //没有历史记录
6. 动态路由
6.1 什么是动态路由
- 在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下路径:
// 1. /user/aaa 或者 /user/bbb // 2. 除了前面的/user之外,后面还跟上了用户的ID // 3. 这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式) // 4. 动态路由绑定映射关系的语法: { path:'/user/:userId', component:User } // 注::userId 是 /user/aaa中的aaa部分,只要是/user/aaa、/user/bbb都可以映射到这个路由 // 在组件中this.$route.params.userId可以获取动态路由的动态路径
6.2 $router
和$route
的区别
- $router: 是路由对象,在每个组件中都可以用
this.$router
访问路由对象 - $route: 是处于活跃状态的路由映射组件,在每个组件中都可以通过
this.$route
访问路由对象中单个活跃组件对象 - 注:在组件的模板中不需要加
this
6.3 动态路由传递参数的方式
- 传递参数有两种方式:
params
和query
- params 的方式:需要传递的数据较少的时候使用
// 1. 配置路由格式: /tputer/:id // 2. 传递方式:在path后面跟上对应的值 <router-link :to="'/home/'+userId" tag='button' replace>首页</router-link> // userId 就是拼接上的值 // 3. 传递后形成的路径: /router/123 //=============================================== // 代码跳转,比如,点击标签触发事件函数跳转 // 在事件函数中: $router.push('/home/'+this.userId)
- query 的方式:需要传递数据较多的时候使用
// 1. 配置路由格式:/profile,也就是普通配置 // 2. 传递方式:对象中使用query的key作为传递方式 <router-link :to="{path:'/profile',query:{name:'张三',age:18}}" >file</router-link> // 3. 传递后形成的路径:/router?id=123 // 4. 通过$route.query可以获取传递的对象 //================================================ // 代码跳转,比如,点击标签触发事件函数跳转 // 在事件函数中: $router.push({ path: '/profile', query: { name: '张三', age: 18 } })
7. 路由懒加载
- 通常路由中会定义很多不同的页面,这些页面最终打包的时候,会放在一个
js
文件中 - 页面越多,
js
文件就会越大,如果一次性从服务器请求下来,可能需要花费一定时间,甚至用户的电脑上还会出现短暂的
空白情况 - 使用懒加载就可以避免这种情况
- 路由懒加载的主要作用是将路由对应的组件打包成一个个的
js
代码块 - 只有在这个路由被访问到的时候,才加载对应组件
- 懒加载的方式:在路由页面中用这种方式导入组件模块
// AMD写法: const About = resolve => require(['../components/About.vue'],resolve) // ES6写法: const Home = () => import('../components/Home.vue') // 开发中推荐使用这种方法导入组件
8. 嵌套路由
- 嵌套路由是一个很常见的功能,比如在
home
页面中,我们希望通过/home/news
和/home/message
访问一些内容 - 一个路径映射一个组件,访问这个路径也会分别渲染两个组件
- 实现路由嵌套的步骤:
// 1. 创建对应的子组件,并且在路由映射中配置对应的子路由 { path:'/home', component:Home, children:[ { path:'homenews', // homenews 前面不需要加 / ,访问的时候会自动 /Home/homenews拼接 component:HomeNews }, { path:'homemessage', component:HomeMessage } ] } // 注:要先导入子组件,用懒加载的方式映射关系中子路径前面不需要加父路径 // 2. 在Home组件内部使用<router-view>标签 <div id="about"> <h2>我是首页</h2> <p>昨夜雨疏风骤</p> <router-link to='/home/homenews'>新闻</router-link> <router-link to='/home/homemessage'>消息</router-link> <router-view></router-view> </div>
9. 导航守卫
- 导航守卫是一个函数:beforeEach
9.1 路由导航实现改变页面 title 标题
-
比如用导航守卫每跳转一个路由页面就改变页面标题
-
在每个路由中创建属性
{ path:'/about', component:About, meta:{ title:'关于' } }
-
实现方法:在路由页面使用路由对象调用方法
beforeEach
// beforeEach是全局导航 router.beforeEach((to, from, next) => { document.title = to.matched[0].meta.title // document.title = 可以改变页面标题 next() // }) // 注:这是路由的方法,路由的页面跳转是因为内部调用了 beforeEach 方法,而方法内部 // 又调用了next()实现的。在这里我们要重写 beforeEach 方法。 // 重写beforeEach方法,是想要在页面跳转的时候改变跳转页面的 title (标题)值 // 方法的的参数是一个函数 // 函数有三个参数:to--->要跳转的目标页面对象 // from--->从这里跳转的当前页面对象 // next()--->实现页面跳转的方法
-
document.title = '首页'
,用DOM对象改变页面的title值。
9.2 路由导航控制访问权限
- 如果用户没有登录就直接通过 URL 访问特定页面,需要重新导航到登录页面
// 1. 为路由对象添加 beforeEach 导航守卫 router.beforeEach((to, from, next) => { // 2. 如果用户访问的是登录页面,直接放行 if (to.path === '/login') return next() // 3. 从 sessionStorage 中获取到保存的 token 值 const tokenStr = window.sessionStorage.getItem('token') // 4. 如果没有 token 就强制跳转到登录页,有的话就直接 next() 跳转 if (!tokenStr) return next('/login') next() }) // to: 跳转的目标页面 // from: 从这跳转的当前页面 // next(): 实现跳转的方法,里面也可以传入参数(要跳转的路由)
10. 路由页面不销毁
-
访问了
a
页面的的子页面,当跳转到别的页面之后,当前页面就会销毁,想要不销毁有一个办法:// 在路由App.vue模板页面: <keep-alive> <router-view></router-view> </keep-alive> // 注:keep-alive可以保持组件不被销毁,而是被缓存起来。 // 如果某些页面不希望被缓存,那么就可以使用keep-alive的一个属性:exclude='组件的name值' // 如果多个页面name值用逗号隔开(不要加空格)
-
如果想回到
a
页面还希望保持离开时的样子,就可以这样做:// 在a页面的组件中: 在data中创建 path:'初始页面' activated(){ // 把页面压栈到路由中显示,进入页面的时候改变页面路径 this.$router.push(this.path) }, beforeRouteLeave(to, from, next){ // 记录离开时的路径,赋值给path属性 this.path = this.$route.path next() } // activated、beforeRouteLeave是组件内导航
11. 路由中的方法
- router.push() 导航到不同的
url
,向history
栈添加一个新的记录。(=== window.history.pushState)// 字符串 router.push('home') // 对象 router.push({ path: 'home' }) // 命名的路由 router.push({ name: 'user', params: { userId: '123' }}) // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }})
声明式 | 编程式 |
---|---|
< router-link :to="" replace> | router.push() |
-
router.replace() 导航到不同 url,替换 history 栈中当前记录。(=== window.history.replaceState)
-
router.go(n)指定前进/回退的步数。n 为正数的时候是前进;负数的时候是后退;0的时候是刷新当前页面。(===window.history.go)
// 在浏览器记录中前进一步,等同于 history.forward() router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 如果 history 记录不够用,那就默默地失败呗 router.go(-100) router.go(100)
-
router. forward():前进一步。
-
router.back():回退一步。
注意:当你点击 时,这个方法会在内部调用,所以说,点击 等同于调用 router.push()。
参考:编程式导航https://router.vuejs.org/zh/guide/essentials/navigation.html
12. vue 组件的 scrollBehavior-滚动行为
- 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
- 注意: 这个功能只在支持 history.pushState 的浏览器中可用。
- 在文档页面拉动滚动条,然后刷新浏览器会发现滚动条依然在原来的位置,这是浏览器的默认行为,会记录浏览器滚动条默认位置。
- 但是点击浏览器“前进/后退”按钮,会发现当初那个页面的滚动条从0开始了,没有记录上一次滚动条的位置。现在要求点击浏览器“前进/后退”按钮,页面滚动条要记录上一次的位置,这时需要设置它的的滚动行为。
- 这时候需要在路由配置中设置 scrollBehavior(to,from,savePosition)函数,函数有三个参数。scrollBehavior() 函数在点击浏览器的“前进/后退”,或者切换导航的时候触发。
// 使用方法 const router = new VueRouter({ routes: [...], scrollBehavior (to, from, savedPosition) { // return 期望滚动到哪个的位置 } }) // scrollBehavior() 方法在点击浏览器的“前进/后退”,或者切换导航的时候触发。 // to:要进入的目标路由对象,到哪里去 // from:离开的路由对象,哪里来 // savePosition:会记录滚动条的坐标,点击前进/后退的时候记录值{x:?,y:?}
- 开发中封装
import Vue from 'vue' import Router from 'vue-router' import { scrollBehavior } from './utils' Vue.use(Router) const router = new Router({ mode: 'history', scrollBehavior, routes: [ ...routesPC, ...routesMO ] }) export default router // 将 scrollBehavior 封装在工具类中
- scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
- 在该方法内,可以通过判断路由to,from两个对象来做一些必要的判断;
- savedPosition 参数是记录的上次滚动的位置;
- 通过return {x:number,y:number}来控制页面滚动的位置;
- 对于所有路由导航,简单地让页面滚动到顶部。
scrollBehavior (to, from, savedPosition) { return { x: 0, y: 0 } }
- 想要在后退时,滚动到上次滚动的位置,如果满足条件,savedPosition有值的情况下:
scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }
- 注意:使用<keep-alive>,scrollBehavior才能生效。
12.2 新增情况:异步滚动
- 当页面数据需要请求加载有延迟的情况下,页面如果直接滚动,会出现滚动后,页面数据请求回来,DOM重新渲染,滚动失效的情况;
- 所以官方文档给补充了异步滚动的方法:
scrollBehavior (to, from, savedPosition) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ x: 0, y: 0 }) }, 500) }) } // 这个会在返回后,有一定延迟再滚动,可以根据自己项目的具体情况进行一定修改,兼容;
- 注:我的项目mobile端数据加载使用的是vue-mugen-scroll滑动加载数据组件,网上没找到能触发它加载的方法,所以,在返回列表页后,数据刷新,只有一页数据,滚动到底,也找不到上次的数据,所以还是没有解决我的问题,但是这个方法是很好的,只是使用情况,会有限制,记录一下,以备后用。