vue-router

258 阅读10分钟

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>

router与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>