vue-router
原理
通过hash模式
通过hash的模式改变url,是不会刷新网页的,然后在router里面监听了hash的变化,映射组件到页面
location.hash = 'foo'
浏览器的url地址:http://localhost:8080/#/foo
浏览器的地址更新了,但是查看Network是没有看到任何请求的
通过h5的history模式
pushState
history.pushState({},'','home')
浏览器的url地址:http://localhost:8080/home
通过h5的history操作,页面也不会刷新,也不会发出请求
history.back()
history的pushState操作和栈结构是一样的,遵循先进后出原则,所以以后push进去的地址都会放在上面
可以通过history.back()方法弹栈出来
replaceState
history.replaceState({},'','home')
使用这个方法来替换掉url地址,用了replaceState是会直接替换掉的,是不会有记录的,不可以后退
go
history.go(-1)
go可以结合pushState一起使用: history.go(-1) 等同于history.back(),往后退一步
go()里面的参数可以正数也可以负数,前进后退都可以
forward
history.forward()
history.forward等同于 history.go(1) ,前进一步
vue-router基本使用
安装
npm install vue-router --save
或者
yarn add vue-router
配置路由
在router文件夹下的index.js文件中:
import Vue from 'vue'
import Router from 'vue-router' //引入路由
import Home from '@/components/Home'
import About from '@/components/About'
Vue.use(Router) //通过Vue.use(插件),安装插件
export default new Router({ //创建路由对象并导出
//配置路由和组件之间的应用关系
routes: [{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
})
还要在main.js中配置一下:
import Vue from 'vue'
import App from './App'
import router from './router' //引入路由
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router, //挂载到vue对象中
render: h => h(App)
})
配置完之后就可以在路由组件中使用了:
App.vue:
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view/>
<!-- router-view 渲染组件必须用到的 -->
</div>
</template>
<router-link>:该标签是一个vue-router中已经内置的组件,它会被渲染成一个<a>标签<router-view/>:该标签会根据当前的路径,动态渲染出不同的组件- 网页的其他内容,比如顶部的标题/导航,或者底部的一些版权信息等会和
<router-view/>处于同一个等级 - 在路由切换时,切换的是
<router-view/>挂载的组件,其它内容不会发生改变
默认路径
如果用户进入页面没有更改url的时候,应该显示首页
routes: [{ //写在最前面,如果路径为空的时候,就重定向到home页面
path: '',
redirect: '/home' //redirect重定向
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
改变路由的模式
默认的情况下,vue-router是使用hash模式的,如果想用history模式,那么也要配置一下:
new Router({
routes: [{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
],
mode:'history' //加上 mode:'history'就可以改变模式了
})
router-link其他属性
tag属性
如果不想让router-link默认渲染成a标签,那么可以使用tag属性更改
<router-link to="/home" tag="button">首页</router-link>
<router-link to="/about" tag="button">关于</router-link>
replace
replace不会留下history记录,所以指定replace的情况下,回退键返回不能返回到上一个页面中
<router-link to="/home" tag="button" replace>首页</router-link>
<router-link to="/about" tag="button" replace>关于</router-link>
active-class
当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称
<template>
<div id="app">
<router-link to="/home" tag="button" replace>首页</router-link>
<router-link to="/about" tag="button" replace active-class="active">关于</router-link>
<router-view/>
<!-- router-view 渲染组件必须用到的 -->
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
.active{
color: pink;
}
</style>
在路由处更改linkActiveClass
router文件夹下的index.js文件:
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import About from '@/components/About'
Vue.use(Router)
export default new Router({
routes: [{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
],
mode: 'history',
linkActiveClass: 'active' //在这里设置这个属性,以后页面中的<router-link>标签点击的时候都会被加上这个 active类属性
})
js代码跳转
<template>
<div id="app">
<button @click="homeClick">首页</button>
<button @click="aboutClick">关于</button>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
methods:{
homeClick(){
// this.$router.push('/home') //push也可以,replace也可以
this.$router.replace('/home')
},
aboutClick(){
// this.$router.push('/about')
this.$router.replace('/about')
}
}
}
</script>
<style>
.active{
color: pink;
}
</style>
route的区别(重点)
$router
1.$router是在router文件夹下的index.js中使用 new Router() new出来对象
2.使用$router可以控制路由的跳转
$route
$route是在vue.prototype的一个对象,是vue内置的一个对象,可以通过这个对象拿到路由的动态参数
动态路由
在router文件夹下的index.js文件中配置一下动态路由:
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import About from '@/components/About'
import User from '@/components/User'
Vue.use(Router)
export default new Router({
routes: [{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/user/:userId', //在此处配置动态路由
component: User
}
],
mode: 'history',
linkActiveClass: 'active'
})
跳转的时候传递参数:App.vue
<template>
<div id="app">
<router-link :to="'/user/'+userId">用户</router-link>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
userId:'111' //把这个参数传递过去
}
}
}
</script>
<style>
.active{
color: pink;
}
</style>
在组件中接收参数:User.app
注意:获得参数使用的是$route
<template>
<div>
<h2>用户页面</h2>
<h3>{{userId}}</h3>
{{$route.params.userId}} //在这里拿的话就不用写this了
</div>
</template>
<script>
export default {
name:'user',
computed: {
userId(){
return this.$route.params.userId //在$route中获得这个参数
}
},
}
</script>
<style>
</style>
路由懒加载
router文件夹下的index.js文件:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [{
path: '',
redirect: '/home'
},
{
path: '/home',
component: () => import ('@/components/Home') //在此处引入即可
},
{
path: '/about',
component: () => import ('@/components/About')
},
{
path: '/user/:userId',
component: () => import ('@/components/User')
}
],
mode: 'history',
linkActiveClass: 'active'
})
vue-router嵌套路由
实现嵌套路由有两个步骤:
1.创建对应的子组件, 并且在路由映射中配置对应的子路由.
2.在组件内部使用<router-view>标签
实现嵌套路由:
先创建两个子组件,这两个子组件属于Home下面的子组件
HomeMessage.vue:
<template>
<div>
<h3>新闻消息页面</h3>
</div>
</template>
<script>
export default {
name:'HomeMessage'
}
</script>
<style>
</style>
HomeNews.vue:
<template>
<div>
<ul>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
<li>新闻列表</li>
</ul>
</div>
</template>
<script>
export default {
name:'HomeNews'
}
</script>
<style>
</style>
接下来是配置router文件夹下面的index.js了,因为里面的路由已经很多了,所以把路由配置抽离成一个变量
router->index.js:
import Vue from 'vue'
import Router from 'vue-router' //引入路由
Vue.use(Router) //通过Vue.use(插件),安装插件
const Home = () =>
import ('@/components/Home')
const About = () =>
import ('@/components/about')
const User = () =>
import ('@/components/user')
const HomeNews = () => //引入组件
import ('@/components/HomeNews')
const HomeMessage = () =>
import ('@/components/HomeMessage')
const routes = [{
path: '',
redirect: '/home' //redirect重定向
},
{
path: '/home',
component: Home,
children: [{ //嵌套路由的关键:父路由下面写一个children属性,它是一个数组,其他的配置和以前的差不多
path: '', //嵌套路由的默认路径
redirect: 'news'
},
{ //在嵌套路由下面,可以不写前面的/,它可以自动帮我们加上
path: 'news', //http://localhost:8080/home/news 在页面是这样显示的
component: HomeNews
}, {
path: 'message',
component: HomeMessage
}
]
},
{
path: '/about',
component: About
},
{
path: '/user/:userId',
component: User
}
]
const router = new Router({
routes: routes,
mode: 'history',
linkActiveClass: 'active'
})
export default router
接下来在Home组件中写router-view标签就可以了:
Home.vue:
<template>
<div>
<h2>我是首页</h2>
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Home'
}
</script>
<style>
</style>
vue-router参数传递
传递参数主要有两种类型:params和query
params的类型:
●配置路由格式: /router/:id ●传递的方式: 在path后面跟上对应的值 ●传递后形成的路径: /router/123, /router/abc
query的类型:
●配置路由格式: /router, 也就是普通配置 ●传递的方式: 对象中使用query的key作为传递方式 ●传递后形成的路径: /router?id=123, /router?id=abc
通过query传值:
传递:在App组件中:
<router-link :to="{path:'/profile',query:{name:'小红',age:22,sex:'女'}}">档案</router-link> //:to 后面跟着一个对象,path是路径,query也是一个对象,里面可以传递想要的值
http://localhost:8080/profile?name=小红&age=22&sex=女 //网页中的路径会变成这样
接收:在Profile组件中:
<template>
<div>
我是profile组件
<h2>{{$route.query}}</h2>
</div>
</template>
通过js跳转并传递参数
<template>
<div id="app">
<h1>我是APP组件</h1>
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
userId:'111'
}
},
methods:{
userClick(){
this.$router.push('/user/'+this.userId)
},
profileClick(){
this.$router.push({ //这里也可以传递对象
path:'/profile',
query:{
name:'小红',
age:22,
sex:'女'
}
})
}
}
}
</script>
<style>
</style>
vue-router导航守卫
什么是导航守卫?
● vue-router提供的导航守卫主要用来监听监听路由的进入和离开的. ● vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
导航守卫的使用:
用于实现页面动态导航title
import Vue from 'vue'
import Router from 'vue-router' //引入路由
Vue.use(Router) //通过Vue.use(插件),安装插件
const Home = () =>
import ('@/components/Home')
const About = () =>
import ('@/components/about')
const User = () =>
import ('@/components/user')
const HomeNews = () =>
import ('@/components/HomeNews')
const HomeMessage = () =>
import ('@/components/HomeMessage')
const Profile = () =>
import ('@/components/Profile')
const routes = [{
path: '',
redirect: '/home' //redirect重定向
},
{
path: '/home',
component: Home,
children: [{
path: '',
redirect: 'news'
},
{
path: 'news',
component: HomeNews
}, {
path: 'message',
component: HomeMessage
}
],
meta: { //写上meta,会被导航守卫的to检测到
title: '首页'
}
},
{
path: '/about',
component: About,
meta: { //写上meta,会被导航守卫的to检测到
title: '关于'
}
},
{
path: '/user/:id',
component: User,
meta: {
title: '用户'
}
},
{
path: '/profile',
component: Profile,
meta: {
title: '档案'
}
}
]
const router = new Router({
routes: routes,
mode: 'history',
linkActiveClass: 'active'
})
router.beforeEach((to, from, next) => { //前置钩子(hook)函数 钩子就是回调函数
// 从from跳转到to
document.title = to.matched[0].meta.title //本来可以直接用to.meta.title可以设置的,但是如果是嵌套路由的话就会是undefined,所以要用matched[0]来取数据
console.log(to) //name: undefined
//meta: {title: "关于"}
//path: "/about"
//hash: ""
//query: {}
//params: {}
//fullPath: "/about"
//matched: [{…}]
//__proto__: Object
next() //自己定义导航守卫,就必须要调用一下next方法
})
export default router
前置和后置守卫的区别
router.beforeEach((to, from, next) => {
console.log('前置')
// 从from跳转到to
document.title = to.matched[0].meta.title
next() //自己定义导航守卫,就必须要调用一下next方法
})
router.afterEach((to, from) => {
console.log('后置')
})
如果是后置钩子, 也就是afterEach, 不需要主动调用next()函数
上面使用的导航守卫, 被称之为全局守卫.,还有其他的守卫
● 路由独享的守卫. ● 组件内的守卫.
官网:router.vuejs.org/zh/guide/ad…
导航钩子的三个参数解析:
● to: 即将要进入的目标的路由对象. ● from: 当前导航即将要离开的路由对象. ● next: 调用该方法后, 才能进入下一个钩子
keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性: ● include - 字符串或正则表达,只有匹配的组件会被缓存 ● exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存 ● router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
封装一个tabbar组件
文件路径:
├─components
│ │ MainTabBar.vue
│ │
│ └─tabbar
│ TabBar.vue
│ TabBarItem.vue
│
├─router
│ index.js
│
└─views
├─cart
│ Cart.vue
│
├─category
│ Category.vue
│
├─home
│ Home.vue
│
└─profile
Profile.vue
第一步,封装tabbar容器组件
TabBar.vue: 这个组件主要用于固定样式,中间有个slot插槽,用于显示每一个子组件
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name: "TabBar"
};
</script>
<style>
#tab-bar {
display: flex;
background-color: #f6f6f6;
position: fixed;
left: 0;
bottom: 0;
right: 0;
box-shadow: 0px -1px 1px rgba(100, 100, 100, 0.2);
}
</style>
第二步,封装tabbar里面的子组件:
TabBarItem.vue:这个组件不仅有插槽,还可以监听到点击事件
<template>
<div class="tab-bar-item" @click="itemClick"> //监听点击事件
<div v-if="!isActive"> //用于显示 是否点击了该组件
<slot name="item-icon"></slot> //会被替换的插槽
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :style="activeStyle"> //因为插槽会被替换,所以在外层包裹一个div,样式和逻辑写在这个div里,这样就不会被替换掉
<slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name: "TabBarItem",
props:{
path:String, //接收传递过来的路径
activeColor:{ //接收传递过来的颜色,不传递用默认的颜色
type:String,
default:'red'
}
},
data() {
return {
// isActive: false
};
},
computed: {
isActive(){ //在计算属性里面计算一下当前路径是否等于活跃的路径,如果等于活跃的路径就返回true,返回true只会就会显示name="item-icon-active"这个插槽
return this.$route.path.indexOf(this.path) !== -1
},
activeStyle(){ //动态style
return this.isActive?{color:this.activeColor}:{}
}
},
methods:{
itemClick(){
//点击事件,跳转到传递过来的路径
this.$router.replace(this.path)
}
}
};
</script>
<style>
.tab-bar-item {
flex: 1;
text-align: center;
height: 49px;
}
.tab-bar-item img {
width: 24px;
height: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
</style>
MainTabBar.vue:结合两个子组件,形成一个完整的组件
<template>
<tab-bar>
<tab-bar-item path="/home" activeColor="hotpink"> //传递路径和颜色进去
<img src="../assets/img/tabbar/home.svg" alt slot="item-icon"/> //替换子组件中的slot
<img src="../assets/img/tabbar/home_active.svg" alt slot="item-icon-active"/>
<div slot="item-text">首页</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="hotpink">
<img src="../assets/img/tabbar/category.svg" alt slot="item-icon"/>
<img src="../assets/img/tabbar/category_active.svg" alt slot="item-icon-active"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/cart" activeColor="hotpink" >
<img src="../assets/img/tabbar/shopcart.svg" alt slot="item-icon"/>
<img src="../assets/img/tabbar/shopcart_active.svg" alt slot="item-icon-active"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="hotpink">
<img src="../assets/img/tabbar/profile.svg" alt slot="item-icon"/>
<img src="../assets/img/tabbar/profile_active.svg" alt slot="item-icon-active"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
</template>
<script>
import TabBar from "./tabbar/TabBar";
import TabBarItem from "./tabbar/TabBarItem";
export default {
name:"MainTabBar",
components: {
TabBar,
TabBarItem
}
}
</script>
<style>
</style>
接下来就可在App.vue文件中直接使用了:
<template>
<div id="app">
<router-view></router-view> //要显示的路由占位标签
<main-tab-bar></main-tab-bar> //封装好的tabbar栏
</div>
</template>
<script>
import MainTabBar from './components/MainTabBar'
export default {
name: "App",
components:{
MainTabBar
}
};
</script>
<style>
@import "./assets/css/base.css";
</style>
路径起别名
在脚手架vue-cli2的情况下:
配置
打开build文件夹下的webpack.config.js文件夹:
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'), //在这里可配置起别名
'assets': resolve('src/assets'), //前面是别名,后面是路径
}
},
使用:
import的情况下,可以这样使用:
import MainTabBar from '@/components/MainTabBar' //用@代表src
export default {
name: "App",
components:{
MainTabBar
}
};
导入图片的时候,可以这样使用:
要在前面加一个~波浪线
<tab-bar-item path="/home" activeColor="hotpink">
<img src="~assets/img/tabbar/home.svg" alt slot="item-icon"/> //要在前面加一个~波浪线
<img src="~assets/img/tabbar/home_active.svg" alt slot="item-icon-active"/>
<div slot="item-text">首页</div>
</tab-bar-item>