vue-router的基本使用
什么路由
路由:通过互联网把信息从源头传递到目的地的活动
路由表:本质上是一个映射表,记录了数据包的指向
路由器提供了两种机制:路由和转送
- 路由决定数据包从来源到目的地的路径
- 转送将输入端的数据转移到合适的输出端
vue-router的认识
- vue-router是vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面
- 我们可以其官方网站对其学习:https://router.vuejs.org/zh/
- 在vue-router的单页面应用中,页面的路径的改变就是组件的切换
后端路由阶段
早期的网站开发整个HTML页面由服务器来渲染的
- 服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示
但是一个网站,这么多页面服务器如何处理
- 一个页面有自己对应的网址,也就是URL
- URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理
- Controller进行各种处理,最终生成HTML或者数据,返回给前端
- 这就是一个IO操作
上面的这种操作最后由后端路由
- 当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端
- 这种情况下渲染好的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化
后端渲染的缺点:
- 一种情况是整个页面的模块由后端人员来编写和维护的
- 另一种情况是前端开发人员如果要开发页面,需要通过PHP和JAVA等语言来编写页面代码
- 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起,编写和维护会非常麻烦
前端路由阶段
前后端分离阶段
- 随着Ajax的出现,由了前后端分离的开发模式
- 后端只提供API来返回数据,前端通过AJAX获取数据,并且可以通过JavaScript将数据渲染到页面中
- 这样做最大的优点就是前后端的责任清晰,后端专注于数据上,前端专注于交互和可视化上
- 并且当移动端(IOS/Android)出现后,后端不需要任何处理,依然使用之前的一套api即可
- 目前很多的网站依然采用这种模式
单页面阶段
- 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
- 也就是前端来维护一套路由规则
前端路由的核心是什么呢
- 改变URL,但是页面不进行整体的刷新
- 如何实现呢
HTML5的history模式:pushstate
浏览器窗口有一个history对象,用来保存浏览的历史
- length
- back() 移动到上一个访问页面,等同于浏览器的后退键
- forward():移动到下一个访问页面,等同于浏览器的前进键
- go():接受一个整数作为参数,移动到该整数指定的页面,移动到该整数指定的页面,比如go(1)相当于forward,go(-1)相当于back()
- pushState()用来在浏览历史中添加记录,接受三个参数,
- state :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数,如果不需要这个参数,此处可以填null
- title:新页面的标题,但是所有浏览器目前忽略这个值,因此这里填null
- url:新的网址,必须与当前页面处在同一个域,浏览器的地址栏将显示这个网址
- history.pushstate({},’’,‘foo’),浏览器地址栏立刻显示http://localhost:8080/foo;但并不会跳转到foo,甚至也不会检查foo是否存在,它只是成为浏览历史中的最新记录。总之,pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应
hash
location是javascript里边管理地址栏的内置对象,比如location.href就管理页面的url。
location.hash则可以用来获取或设置页面的标签值
location.hash='aaa'
location.href和location.hash的区别
- window.location.href
- 得到和使用的是完整的url,比如window.location.href="www.baidu.com”表示的是重新定向,页面跳转到新的页面。也可以通过window.location.href得到a标签的完整的href,比如如果使用href,那么可以得到完整的链接(url)
- window.location.hash
- 得到的是锚链接。相比如href,通过window.location.hash并不会跳转到新的链接,只会在当前链接里面改变锚链。并且如果有通过window.location.hash得不到完整的链接(URL),仅仅得到#book.
安装和使用vue-router
- router-link该标签是一个vue-router中已经内置的组件,它会被渲染成一个a标签
- router-view该标签会根据当前的路径,动态渲染出不同的组件
- 网页的其他内容,比如顶部的标签/导航,或者底部的一些版权信息等会和router-view处于一个等级
- 在路由切换时,切换的是router-view挂载的组件,其他内容不会发生改变
步骤:
-
安装vue-router, npm install vue-router --save
-
导入路由对象,并且调用vue.use(vuerouter)
use函数会调用VueRouter的install -
创建路由实例,并且传入路由映射设置
-
在vue实例中挂载创建的路由实例
router/index.js
///router/index.js**
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
//1 通过vue.use(插件), 安装插件
Vue.use(VueRouter)
//2 创建vuerouter实例
const routes =[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
];
//需要传入RouterOptions对象
//在RouterOptions对象中需要传入routes属性值,是一个数组
const router = new VueRouter({
routes
})
//3 将router实例传入到vue实例中
export default router
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
///main.js*
import Vue from 'vue'
import App from './App'
import router from './router/index'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
router:router,
template: '<App/>'
})
///App.vue
///App.vue
<template>
<div id="app">
<router-link to="/home">Home</router-link>
<router-link to="/about">About</router-link>
<!-- <Home></Home> -->
<!-- 在那里显示 -->
<router-view></router-view>
</div>
</template>
<script>
import Home from './components/Home.vue'
import About from './components/About.vue'
export default {
name: 'App',
components: {
Home
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
路由的默认路径
默认情况下,进入网站的首页,我们希望router-view渲染首页的内容。可以redirect实现
配置解析:
我们在router中又配置了一个映射
path配置的是根路径
redirect是重定向,也就是我们将路径重定向到/home的路径下
去除#改变路由和组件之间的应用关系
改变路径的两种方式:
- URL的hash
- HTML5的history
- 默认情况下,路径的改变使用URL的hash
- 如果希望使用HTML5中的history模式,非常简单,进行如下配置就可以
在VueRouter实例中添加mode
const router = new VueRouter({
routes,
mode:'history'
})
改变router-link的标签
router-link中的属性
- tag:tag可以指定router-link之后渲染成什么组件,比如下面的代码会渲染成button而不是a
<router-link to="/home" tag="button">Home</router-link>
<router-link to="/about" tag="button">About</router-link>
- replace:replace不会留下history记录,所以指定replace的情况下,后退键返回不能返回到上一个页面中
- active-class :当router-link对应的路径匹配成功时,会自动给当前的元素设置一个router-link-active的class,设置active-class可以修改默认的名称
- 在进行高亮显示的导航菜单或者底部tabbar时,会使用到该类
- 但是通常不会修改类的属性,会直接使用默认的router-link-active即可
<template>
<div id="app">
<router-link to="/home" tag="button" active-class="active">Home</router-link>
<router-link to="/about" tag="button" active-class="active">About</router-link>
<router-view></router-view>
</div>
</template>
<style>
.active{
color: red;
}
</style>
设置active-class的两种方式:
- 在router-link中设置
<router-link to="/home" tag="button" active-class="active">Home</router-link>
- 在vuerouter实例中设置
const router = new VueRouter({
routes,
mode:'history',
linkActiveClass:'active'
})
通过代码实现路由跳转
通过注入路由器,我们可以在任何组件内通过 this.
r
o
u
t
e
r
访
问
路
由
器
,
也
可
以
通
过
t
h
i
s
.
router 访问路由器,也可以通过 this.
router访问路由器,也可以通过this.route 访问当前路由
this.$route 表示当前路由对象,每一个路由都会有一个 route 对象,是一个局部的对象,可以获取对应的 name, path, params, query 等属性。
App.vue
<template>
<div id="app">
<button @click="homeClick">Home</button>
<button @click="aboutClick">About</button>
<router-view></router-view>
</div>
</template>
<script>
import Home from './components/Home.vue'
import About from './components/About.vue'
export default {
name: 'App',
components: {
Home
},
methods:{
homeClick(){
this.$router.replace('/home');
},
aboutClick(){
this.$router.replace('/about');
}
}
}
</script>
动态路由设置
在某些情况下,一个页面的path路径可能时不确定的,比如我们进入用户界面时,希望是如下的路径:
- /user/aaa/bbb
- 除了又前面的/uer之外,后面还跟上用户的id
- 这种path和component的匹配关系,我们称之为动态路由(也是路由传递参数的一种的方式)
注意:通过:to动态绑定id时,前面的字符串需要加单引号
index.js
const routes =[
{
path:'/user/:id',
component:User
}
];
App.vue
<template>
<div id="app">
<router-link :to="'/user/'+id">User</router-link>
<router-view></router-view>
</div>
</template>
User.vue
通过this.$route.params获取参数
//在template中不需要加this
<template>
<div>
<h2>User</h2>
<p>我是User组件</p>
<p>{{$route.params.id}}</p>
</div>
</template>
路由的懒加载
- 当打包构建应用时,JavaScript包会变得非常大,影响页面加载
- 如果我们能把不同路由对应得组件分割成不同得代码块,然后当路由被访问得时候才加载对应组件,这样就更加高效了
- 官方在说什么,我们知道路由中通常会定义很多不同得页面,这个页面最后被打包在那里呢?一般情况下,是放在一个js文件中。但是页面这么多放在一个js文件中,必然会造成这个页面非常得大。如果我们一次性从服务器中请求下来这个页面,可能需要花费一定得时间,甚至用户得电脑上还出现了短暂得空白情况。如何
app.js是当前应用程序开发得所有代码业务
mainifest.js:为了打包代码得底层支撑
vendor.js:提供第三方
懒加载实现方式
方式一:结合vue的异步组件和webpack的代码分析
const Home=resolve=>{require.ensure(['../components/Home.vue'],()=>{resolve(require('../components/Home.vue'))})};
方式二:AMD写法
const About = resolve=>require(['../components/About.vue'],resolve)
方式三:在Es6中的实现方式
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const User = () => import('../components/User')
const routes =[
{
path:'/',
redirect:'/home',
},
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
},
{
path:'/user/:id',
component:User
}
];
vue-router嵌套路由
嵌套路由是一个很常见的功能:
- 比如在home页面中,我们希望通过/home/news和/home/messages访问一些内容
- 一个路径映射一个组件,访问这两个路径也会分别渲染两个组件
实现嵌套路由有两个方案 - 创建对应的子组件,并且在路由映射中配置对应的子路由
- 在组件内部使用router-view
router/index.js
const routes =[
{
path:'/home',
component:Home,
children:[
{
path:'/',
redirect:'/news'
},
{
path:'/news',
component:News,
},
{
path:'/message',
component:Message,
}
]
},
];
home.vue
<template>
<div>
<h2>Home</h2>
<p>我是home组件</p>
<router-link to="/news">News</router-link>
<router-link to="/message">Message</router-link>
<router-view></router-view>
</div>
</template>
vue-router参数传递
传递参数主要有两种类型:prarms和query
- params的类型
- 配置路由格式/router/:id
- 传递的方式:在path后面跟上对应的值
- 传递后形成的路径:/router/123,router/abc
- query的类型
- 配置路由的格式;对象中使用query的key作为传递方式
- 传递的方式,对象中使用的query的key作为传递方式
- 传递后形成的路径:/router?id=123
<router-link :to="{path:'/news',query:{name:'aaa',age:'19'}}">News</router-link>
vue-router和route的由来
{
$router:this._routerRoot._router
}
//以上写法跟上面是一致的,都是在对象中新增属性
Object.defineProperty(Vue.prototype,'$router',{
get(){return this._routerRoot._router}
})
所有组件都继承自vue的原型
例如:在vue原型中增加方法
Vue.prototype.test=function(){
console.log("test")
}
在任意组件中调用原型方法
aboutClick(){
this.test();
//this.$router.replace('/about');
this.$router.push({
path:'/about',
query: {
name: 'kobe',
age: 19
}
})
}
结论:router就是vue原型中属性,又因为所有组件都继承自vue原型,所以所有组件拿到的router都是一样的,和main.js中导入的router是同一个
vue-router的导航守卫
学习url:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
需求:切换路由的时候,显示title
实现:通过钩子函数实现
export default {
name: "About",
created(){
document.title="About";
},
}
export default {
name: "Home",
created(){
document.title="首页";
},
}
export type 取别名的写法
利用meta和beforeEach实现
const routes =[
{
path:'/home',
component:Home,
meta:{
title:'home',
},
children:[
{
path:'/',
redirect:'/news'
},
{
path:'/news',
component:News,
},
{
path:'/message',
component:Message,
}
]
},
{
path:'/about',
component:About,
meta:{
title:'about',
},
},
//路由守卫
router.beforeEach((to,from,next)=>{
next();//必须调用
document.title=to.matched[0].meta.title;
})
- to:即将要进入的目标的路由对象
- from:当前导航即将要离开的路由对象
- next:调用该方法后,才能进入下一个钩子
- beforeEach一般用来做一些进入页面的限制。比如没有登录,就不能进入某些页面,只有登录了之后才有权限查看某些页面。。。说白了就是路由拦截。
- afterEach,后置钩子,不需要主动的调用next函数
- 全局守卫
const router = new VueRouter({...})
router.beforeEach({to,from,next})=>{
...
}
- 路由的独享守卫
const router = new VueRouter({
path:'/foo',
component:foo,
beforeEnter:(to,from,next)=>{
...
}
})
- 组件内的守卫
const foo = {
template:``,
beforeRouterEnter(to,from,next){
//再渲染该组件的对应路由被confirm前调用
//不能获取组件实例
//因为当守卫执行前,组件实列还没创建
}
}
keep-alive
-
keep-alive是vue内置的一个组件,可以使被包含的组件保留状态,或避免重复创建和渲染
- 它们有两个非常重要的属性
- inculde 字符或正则表达式,只有匹配的组件会被缓存
- exclude 字符串或正则表达式,任何匹配的组件都不会被缓存
-
activated和deactivated只有在keep-alive存在的情况下才能用
-
router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到视图组件都会被缓存
`
//逗号之间不能有空格
//Home使Home组件的name
<keep-alive include = "Home" exclude= "About,User">
//所有匹配到的视图组件都会被缓存
<router-view></router-view>
</keep-alive>
`
//App.vue
<keep-alive>
<router-view></router-view>
</keep-alive>
export default {
name: "Home",
created(){
console.log("home被创建");
},
data: function () {
return {
path:'/message',
}
},
activated:function(){
console.log(this.path);
this.$router.push(this.path);
},
//时机太晚,获取到的还是已经被激活的路由路径
// deactivated:function(){
// console.log(this.$route.path);
// },
beforeRouteLeave(to,from,next){
console.log(this.$route.path);
this.path = this.$route.path;
next();
}
}
案列
TabBar的实现思路
//topbar
<template>
<div class="topbar">
<top-bar-item path='/home' activeColor="blue">
<div slot="item-icon" >img</div>
<div slot="item-text" >首页</div>
</top-bar-item>
<top-bar-item path='/category'>
<div slot="item-icon" >img</div>
<div slot="item-text" >分类</div>
</top-bar-item>
<top-bar-item path="/cart">
<div slot="item-icon">img</div>
<div slot="item-text" >购物车</div>
</top-bar-item>
<top-bar-item path="/mine">
<div slot="item-icon">img</div>
<div slot="item-text" >我的</div>
</top-bar-item>
</div>
</template>
<script>
import TopBarItem from './TopBarItem'
export default {
name: "TopBar",
data(){
return{
}
},
components: {
TopBarItem,
},
computed:{
}
}
</script>
<style >
.topbar{
display: flex;
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #f6f6f6;
height: 49px;
}
</style>
//topbaritem
<template>
<div class="topbaritem" @click="itemclick">
<slot name="item-icon"></slot>
<div :style="acitveStyle" >
<slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name: "TopBarItem",
data(){
return{
}
},
props:
{
path:String,
activeColor:{
type:String,
default:'red'
}
},
computed:{
isActive(){
console.log("isActive"+this.$route.path)
var ret = this.$route.path.indexOf(this.path)!=-1;
return ret;
},
acitveStyle(){
var ret =this.isActive? {color:this.activeColor}:{};
return ret;
}
},
methods:{
itemclick() {
console.log("itemclick"+this.$route.path);
if (this.path != undefined&&this.$route.path.indexOf(this.path)==-1) {
this.$router.replace(this.path)
}
}
}
}
</script>
<style >
.topbaritem{
flex: 1;
text-align: center;
}
.active{
color: red;
}
</style>
//router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
const Home = ()=>import('../pages/home/Home')
const Cart = ()=>import('../pages/cart/Cart')
const Category = ()=>import('../pages/category/Category')
const Mine = ()=>import('../pages/mine/Mine')
Vue.use(VueRouter)
const routes=[
{
path:'/home',
component:Home,
},
{
path:'/cart',
component:Cart
},
{
path:'/category',
component:Category
},
{
path:'/mine',
component:Mine
}
]
const router = new VueRouter({
routes,
mode:'history',
})
export default router
//App.vue
<template>
<div id="app">
<router-view></router-view>
<TopBar></TopBar>
</div>
</template>
<script>
import TopBar from './components/topbar/TopBar'
export default {
name: 'App',
components: {
TopBar,
}
}
</script>
<style>
</style>
//main.js
import Vue from 'vue'
import App from './App'
import router from './router/index'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router:router,
render: h => h(App)
})
文件路径引用
修改build/webpack.base.conf.js
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
'components':resolve('src/components'),
}
},
用法:
如果文件是通过import引入,可以直接使用别名
import TopBarItem from 'components/topbar/TopBarItem'
如果文件不是通过import引入,需要在别名前面加一个~符号来使用,否则会找不到文件
<img src="~assets/img/tabbar/home.png" />
补充
关闭丑化