vue插件--路由vue-router

2 阅读6分钟

vue插件--路由vue-router(vue.js官方的路由插件,适合用于构建单页面应用。)

可单独安装或脚手架安装

单页应用(全称 single-page application,简称 SPA)

React、 Vue、Angular等前端框架均采用了SPA原则。

它是一种网站应用的模型,可以动态重写当前的页面来与用户交互,而不需要重新加载整个页面。通俗讲就是整个应用只有一个html页面,页面跳转切换就相当于是组件切换,不同哈希值对应不同组件,比多页应用更流畅。

vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。

npm install vue-router@4 --save 安装router插件

一、路由基础

1. 在根目录下创建路由模块文件夹router->index.js(用于配置路由信息)

1>.导入路由插件 2>.创建路由对象配置路由规则 3>.暴露路由模块

index.js

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'

const routes = [
  {
    path'/',
    redirect'/login',
  },

  {
    path'/login',
    componentLogin,
  },

  {
    path'/home',//路径路由
    name'home',//命名路由
    componentHome,
    redirect'/user', // 重定向到user路由
    children: [
      {
        path'/user',
        component() => import('@/views/User.vue'),//懒加载
      },

      {
        path'/A',
        component() => import('@/views/A.vue'),
      },

      {
        path'/B',
        component() => import('@/views/B.vue'),
      },
    ],
  },

  // 404路由不存在匹配,放在路由最下面
  {
    path'/:pathMatch(.*)*',
    component() => import('@/views/NotPage.vue'),
  },
]

const router = createRouter({
  // history: createWebHashHistory(),//路由模式,hash模式
  historycreateWebHistory(),//路由模式,history模式 
  routes,//路由规则
})

export default router

2. main.js入口文件中集成路由插件

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

3. App.vue根组件,类似插槽,路由输出组件的位置

<template>
<div>
<!-- 路由跳转,类似a标签 -->
<!-- <router-link to="/home">跳转到首页</router-link> -->
        <!-- 路由输出位置 -->
        <router-view></router-view>
    </div>
</template>

<style scoped></style>

4. vite.config.js

import vue from '@vitejs/plugin-vue' // vite构建工具解析 .vue文件的插件
import {defineConfig} from 'vite'//defineConfig方法,编写代码会有提示
import { fileURLToPath, URLfrom "node:url";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],// 集成插件
  resolve: {
    alias: {
      "@"fileURLToPath(new URL("./src", import.meta.url)),// @符号代替./src
    },
  },
});

二、路由模式、路由导航、路由传参、命名视图、嵌套路由(配置路由规则children);路由元信息(属性)、滚动行为

1.两种路由模式:

  • createWebHashHistory,Hash模式(默认)

使用URL的hash来模拟一个完整的URL,当URL改变时,页面不会重新加载。示例:http://ip:port/ #/login->http://ip:port/ #/main。 #不美观

  • createWebHistory,History模式

利用history对象的 history.pushState API来完成 URL 跳转而无须重新加载页面。url地址像正常请求一样,比hash模式美观

2.两个全局组件:

  • router-view路由输出组件位置

命名视图:在Vue.js中用于定义具有多个视图的路由配置的一种方式。如:一个路由对应多个组件,如一个页面包含侧边栏和内容区域

<router-view></router-view>

<router-view name="slider/content"></router-view>

index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import Slider from '@/views/Slider.vue'
import Content from '@/views/Content.vue'
// 路由规则
const routes = [
    {
        name'login',
        path'/login', // http://ip:port/#login
        componentLogin,
    },
    {
        path'/main',
        components: {//命名视图
            sliderSlider,
            contentContent,
        },
    },
]

// 创建路由对象
const router = createRouter({
    historycreateWebHashHistory(), // 路由模式 使用hash模式 history模式
    routes, // router  $router.push()   route   $route.query $route.parmas    routes
})
export default router
  • router-link路由跳转
<router-link to="/home">跳转到首页</router-link>
<router-link :to="{ name: 'home' }">name跳转到首页</router-link>
<router-link :to="{path:'/home'}">path跳转到首页</router-link>

3.全局属性:

  • $route路由信息对象,获取路由参数
  • $router路由对象

4.路由元信息(属性)、滚动行为

路由元信息meta:{isok:true}可以自定义属性

滚动行为scrollBehavior 方法

index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import Login from '@/views/Login.vue'

// 路由规则
const routes = [
{
      path'/',
      redirect'/login',
    },
    {
        name'login',
        path'/login', // http://ip:port/#login
        componentLogin,
    },
    {
        name'home',
        path'/home', //http://ip:port/#home
        component() => import('@/views/Home.vue'), // 懒加载, 组件的懒加载, 图片懒加载
        meta:{isok:true}//访问这个路由需要isok为真才能访问
    },

]

// 创建路由对象
const router = createRouter({
    historycreateWebHashHistory(), // 路由模式 使用hash模式 history模式
    routes,
    scrollBehavior(to, from,savedPosition){//滚动行为
        //始终滚动到顶部
        retuna(top:0}
    },
})

//获取meta元信息中的属性
router.beforeEach((to, from,next) => {//前置导航守卫
  if (to.matched.some(record-> record.meta.isok)){//matched路由数组
    // 此路由需要身份验证,检查是否已登录,如果没有,则重定向到登录页面。
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    }else {
      next()
    }
  }else{
    next()//确保一定要调用next()
  })

export default router

5.路由传参

  • 路由query传参(router-link query传参,$route.query获取路由参数)
  • 动态路由传参(路由配置时,路径后加 /:参数名,router-link路径后加/要传的参数 传参,$route.params接收参数)
  • 编程导航(router.push()压栈,返回会回到前一页;router.push()压栈,返回会回到前一页;router.replace()压栈前先把原来的弹出去,直接把原来的替换掉)

示例:

1.>main.js入口文件/vite.config.js配置文件同上

2>.index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import Login from '@/views/Login.vue'
import Slider from '@/views/Slider.vue'
import Content from '@/views/Content.vue'

// 路由规则
const routes = [
{
      path'/',
      redirect'/login',
    },
    {
        name'login',
        path'/login', // http://ip:port/#login
        componentLogin,
    },
    {
        name'home',
        path'/home', //http://ip:port/#home
        component() => import('@/views/Home.vue'), // 懒加载, 组件的懒加载, 图片懒加载
    },
    {
        path'/detail/:id',//动态路由传参
        component() => import('@/views/Detail.vue'),
},
{
        path'/nba',//编程导航
        component() => import('@/views/Nba.vue'),
},
{
        path'/main',
        components: {//命名视图
            sliderSlider,
            contentContent,
        },
    },
{
        path'/music',
        component() => import('@/views/Music.vue'),
        // 配置嵌套路由  一个路由组件中嵌套输出另外一个路由
        children: [
            {
                path'/index',
                component() => import('@/views/Index.vue'),
            },
            {
                path'/song',
                component() => import('@/views/Song.vue'),
            },
        ],
    },
]

// 创建路由对象
const router = createRouter({
    historycreateWebHashHistory(), // 路由模式 使用hash模式 history模式
    routes, // router  $router.push()   route   $route.query $route.parmas    routes
})
export default router

3>.App.vue根组件传参

<template>
<div>
        <!--<router-link :to="{path:'/home'}">path跳转到首页</router-link> -->
        <router-link :to="{ name: 'home' }">name跳转到首页</router-link>
        <!--对象形式,通过query传参到登录界面-->
        <router-link :to="{ path: '/login', query: { message: 'hello' } }">登录界面</router-link>
        <!--动态路由传参-->
        <ul>  
            <li>
                <router-link :to="{ path: '/detail/1' }">商品1</router-link>
            </li>
            <li>
                <router-link :to="{ path: '/detail/2' }">商品2</router-link>
            </li>
        </ul>
<!--编程导航-->
        <button @click="bindNavgatorNBA">NBA</button>
        <!-- 路由输出位置 -->
        <router-view></router-view>

        <!-- 命名视图,一个路由对应多个组件(如一个页面包括侧边栏和内容区域) -->
        <router-view name="slider"></router-view>
        <router-view name="content"></router-view>
    </div>
</template>
<script>
export default {
    data() {
        return { }
    },

    methods: {
        bindNavgatorNBA() {
            // 切换NBA路由组件
            // $router 路由对象
            // $route 路由信息对象
            this.$router.push({ path'/nba' })
        },
    },
}
</script>

4>.Login.vue获取参数

<template>
    <div>
        <h2>登录界面</h2>
        <p>{{ $route.query.message }}</p>
    </div>
</template>
<script>
/**
 * $route
 *   路由信息对象
 *     作用:获取路由参数
 */

export default {
    mounted() {
        console.log(this.$route.query.message)
    },
}
</script>

5>.Detail.vue接收参数

<template>
  <div>
    <h2>商品详情</h2>
    <p>{{ $route.params.id }}</p>
  </div>
</template>

6.导航守卫(在进行路由切换/跳转时,执行的一些钩子函数)

分类:

  1. 全局的,只要路由跳转都会执行(全局前置守卫、全局解析守卫、全局后置守卫)

  2. 路由独享,只在这个路由触发(路由规则表中写)

  3. 组件内 (组件中写)

1.全局

  • 全局前置守卫

router.beforeEach((to要进入的目标,from当前导航要离开的路由)=>{return false//不放行})

统一登陆身份认证,没有登陆不许访问需登陆的界面。

根目录下封装一个单独的permission.js文件,也可直接在router→index.js文件中写

permission.js

import router from './router'

router.beforeEach((to, from) => {//to要进入的目标,from当前导航要离开的路由
    // console.log('全局前置导航守卫 >>>>')
    // 1. 加入白名单: 有些路由是不需要登录身份认证 path: /login ,  /
    if (to.path === '/login') {
        return true //放行
    }

    // 2. 登录认证
    let user = localStorage.getItem('USER')
    if (user) {
        return true // 放行
    } else {
        // 如果不存在,重定向到登录界面
        router.replace({ path'/login' })
    }
})

Login.vue

<!-- eslint-disable vue/multi-word-component-names -->
<template>
    <div class="g-container">
      <div class="g-wrapper">
          <h2>xx平台</h2>
          <form @submit.prevent="bindLogin">
            <input
                type="text"
                name="name"
                placeholder="请输入用户"
                v-model="user.name"
            /><br />
            <input
                type="password"
                name="password"
                placeholder="请输入密码"
                v-model="user.password"
            /><br />
            <p>{{ message }}</p>
            <input type="submit" class="m-login-btn" value="登录" />
        </form>
      </div>
  </div>
</template>

<script>
export default {
    data() {
        return {
            user: {
                name'',
                password'',
            },
            message'',
        }
    },
    methods: {
        bindLogin() {
            if (this.user.name === 'admin' && this.user.password === '123') {
                // 1. 保存登录状态
                localStorage.setItem('USER', JSON.stringify(this.user))

                // 2. 跳转主界面
                this.$router.push({ path'/home' })
            } else {
                this.message'用户名或密码出错!'
            }
        },
    },
}
</script>

<style lang="scss" scoped>
.g-container {
  width: 100%;
  height: 100vh;
  background-color: #2b3c4d;
  position: relative;
  .g-wrapper {
      width: 500px;
      height: 400px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      h2 {
          text-align: center;
          color: white;
          margin-bottom: 20px;
      }
      form {
          width: 100%;
          background-color: white;
          border-radius: 8px;
          text-align: center;
          padding: 20px 0;
          input {
              width: 80%;
              margin: 20px 0;
              outline: none;
              height: 30px;
              border: 1px solid gray;
              border-radius: 5px;
              text-indent: 10px;
          }
          .m-login-btn {
              background-color: #409eff;
              color: white;
              height: 40px;
              border: none;
              border-radius: 5px;
              margin-bottom: 20px;
              &:hover {
                  cursor: pointer;
                  background-color: #64a5e7;
              }
          }
      }
  }
}
</style>
  • 全局解析守卫

router.beforeResolve 和 router.beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

  • 全局后置守卫

router.afterEach((to,from)=>{return false})不会接受next函数也不会改变导航本身。

加载进度条

2.路由独享守卫

只在进入路由时触发,不会在 params、query 或 hash 改变时触发。如,从 /users/2 进入到 /users/3,只有从一个不同的路由导航时,才会被触发。

router.beforeEnter((to,from)=>{return false//不放行})

const routes=[
{
  path: '/users/:id',
  component: UserDetails,
  beforeEnter:(to, from,next) => {
  //reject the navigation
  return false
  }
}
]

3.组件内守卫(组件里面定义)

通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。

beforeRouteEnter(to,from,next){//进入的时候执行

//在渲染该组件的对应路由被验证前调用

//不能获取组件实例this,因为当守卫执行时,组件实例还没被创建

//可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数

next(vm => { // 通过 `vm` 访问组件实例 })

}

beforeRouteUpdate(to,from){//更新的时候执行

//在当前路由改变,但是该组件被复用时调用

//举例:一个带有动态参数的路径‘/users/:id,在‘/users/1和‘/users/2之间跳转时,由于会渲染同样的“UserDetails组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用

//因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例‘this

}

beforeRouteLeave(to,from){//离开的时候执行

//在导航离开渲染该组件的对应路由时调用

//与beforeRouteupdate一样,它可以访问组件实例"this"

const answer = window.confirm('Do you really want to leave? you have unsaved changes!')

 if (!answer) return false

}

4.完整的导航解析流程

1. 导航被触发。

2. 在失活的组件里调用 beforeRouteLeave 守卫。

3. 调用全局的 beforeEach 守卫。

4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。

5. 在路由配置里调用 beforeEnter。

6. 解析异步路由组件。

7. 在被激活的组件里调用 beforeRouteEnter。

8. 调用全局的 beforeResolve 守卫(2.5+)。

9. 导航被确认。

10. 调用全局的 afterEach 钩子。

11. 触发 DOM 更新。

  1. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。