Vue3路由 保姆级教程

1,272 阅读5分钟

Vue-Router

  • 路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:

    • 后端路由阶段
    • 前后端分离阶段
    • 单页面富应用(SPA)

1.URL之hash

  • 前端路由是如何做到URL和内容进行映射呢?监听URL的改变

  • URL的hash

    • URL的hash也就是锚点#,本质上是改变window.location的href的属性
    • 可以通过直接赋值location.hash来改变href,但是页面不发生刷新!
  • 下面这个代码片段就能很好的解释:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div id="app">
    <a href="#/home">home</a>
    <a href="#/about">about</a>
​
    <div class="content">Default</div>
  </div>
​
  <script>
    const contentEl = document.querySelector('.content');
    window.addEventListener("hashchange", () => {
      switch(location.hash) {
        case "#/home":
          contentEl.innerHTML = "Home";
          break;
        case "#/about":
          contentEl.innerHTML = "About";
          break;
        default:
          contentEl.innerHTML = "Default";
      }
    })
  </script></body>
</html>

[注意⚠️]  hash的优势就是兼容性好,但是#显得不像是一个真实路径


2.HTML5之History

  • history接口是HTML5新增的, 一共有六种模式改变URL而不刷新页面:
模式说明
replaceState替换原来的路径
pushState使用新的路径
popState路径的回退
go向前或向后改变路径
forward向前改变路径
back向后改变路径

接下来这个案例是实现history模式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <a href="/home">home</a>
    <a href="/about">about</a>
​
    <div class="content">Default</div>
  </div>
​
  <script>
    const contentEl = document.querySelector('.content');
​
    const changeContent = () => {
      console.log("-----");
      switch(location.pathname) {
        case "/home":
          contentEl.innerHTML = "Home";
          break;
        case "/about":
          contentEl.innerHTML = "About";
          break;
        default: 
          contentEl.innerHTML = "Default";
      }
    }
​
    const aEls = document.getElementsByTagName("a");
    for (let aEl of aEls) {
      aEl.addEventListener("click", e => {
        e.preventDefault();
        
        const href = aEl.getAttribute("href"); /home /about
        // history.pushState({}, "", href);
        history.replaceState({}, "", href);
​
        changeContent();
      })
    }
​
    window.addEventListener("popstate", changeContent)
​
    
​
  </script>
</body>
</html>

3.认识vue-router

  • 目前三大主流框架都有自己的路由实现

  • VueRouter使构建单页面应用变得非常easy

  • vue-router使基于路由和组件的

    • 路由用于设定访问路径,将路径和组件映射起来
    • 在vue-router的单页面应用中,页面的路径改变就是组件的切换
  • 安装Vue Router

npm install vue-router

4.使用步骤

  1. 创建路由组件
  2. 配路由映射,组件和路径映射关系的routes数组
  3. 通过createRouter创建路由对象,并且传入routes和history模式
  4. 使用路由:与
  • 创建pages页面并且放入对应的组件
  • About.vue
<template>
  <div>
    <h2>about</h2>
  </div>
</template><script>
  export default {
    
  }
</script><style scoped></style>
  • Home.vue
<template>
  <div>
    <h2>home</h2>
  </div>
</template><script>
  export default {
    
  }
</script><style scoped></style>
  • 有个规定就是在src目录下创建一个router文件夹放入index.js进行路由的导入并且配置
//从下载的vue-router中导入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from "vue-router"
import Home from "@/pages/Home.vue"
import About from "@/pages/About.vue"
//配置映射关系
const routes = [
  //默认
  {
    path:'/',
    //重定向
    redirect:"/home"
  },
  {
    path:"/home",
    component:Home
  },
  {
    path:"/about",
    component:About
  }
]
​
//创建路由对象
const router = createRouter({
  routes,
  history:createWebHistory()
})
​
export default router
  • 如何道main.js中进入使用
//main.js
import {createApp} from "vue"
import App from './App.vue'//const app = createApp(App)
//安装插件
//app.use(router)
//app.mount("#app")
//链式调用
createApp(App).use(router).mount("#app")
  • 再到App.vue中使用,就好比一个最大的页面里面包着很多组件
  • APP.vue
<template>
  <div id="app">
    <router-view></router-view>
    <!-- 
      1.to属性就是去哪里,所以点击之后就会跳转到相对应页面
      2.设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push();
      3.active-class属性:设置激活a元素后应用的class,默认是router-link-active
      4.exact-active-class属性:链接精准激活时,应用于渲染的 <a> 的 class,默认是router-link-exact-active;
    -->
    <router-link to="/home" replace active-class="p-active">首页</router-link>
    <router-link to="/about" replace active-class="p-active">关于</router-link>
  </div>
</template>
<script>
  export default {
    name:"App",
    components: {
      
    }
  }
</script>
<style>
  /* 
    .router-link-active {
      color:red
    }
  */
  .p-active {
    color:red
  }
  
</style>

5.路由懒加载

  • 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载:

    • 如果能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效
    • 可以提高首屏的渲染效率;
  • 其实这里还是我们前面讲到过的webpack的分包知识,而Vue Router默认就支持动态来导入组件

    • 这是因为component可以传入一个组件,也可以接收一个函数,该函数 需要放回一个Promise;
    • 而import函数就是返回一个Promise;
//从下载的vue-router中导入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from "vue-router"
//import Home from "@/pages/Home.vue"
//import About from "@/pages/About.vue"
//配置映射关系
const routes = [
  //默认
  {
    path:'/',
    //重定向
    redirect:"/home"
  },
  {
    path:"/home",
    name:"home"
    component:()=> import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue"),
    meta: {
      name:"Tim",
      age:21,
      height:1.88
    }
  },
  {
    path:"/about",
    name:"about",
    component:()=> import("../pages/About.vue")
  }
]
​
//创建路由对象
const router = createRouter({
  routes,
  history:createWebHistory()
})
​
export default router

6.路由的其他属性

  • name属性:路由记录独一无二的名称
  • meta属性:自定义的数据


7.动态路由基本匹配

  • 很多时候需要将给定匹配模式的路由映射到同一个组件:

    • 假设有一个User组件,应该是对所有用户进行渲染,但是用户ID是不同的
    • 在vue-router中,可以在路径中使用一个动态字段来实现,称之为路径参数
//路由规则
{
  path:"/user/:id",
  component:()=> import("../pages/User.vue")
}
  • User.vue
<template>
  <div>
    <h2>User: {{$route.params.id}}</h2>
  </div>
</template><script>
  import {useRoute} from "vue-router"
  export default {
    //created() {
      //this.$route.params.id
    //}
    setup() {
      const route = useRoute()
    }
  }
</script><style scoped></style>
  • App.vue:在页面中进行id匹配之跳转
<template>
  <div id="app">
    <router-view></router-view>
    <router-link to="/user/123"></router-link>
  </div>
</template>
<script>
  export default {
    name:"App",
    components: {
      
    }
  }
</script>
<style>
  /* 
    .router-link-active {
      color:red
    }
  */
  .p-active {
    color:red
  }
  
</style>

匹配多个参数

//路由规则
{
  path:"/user/:id/info/:name",
  component:()=> import("../pages/User.vue")
}
匹配模式匹配路径$route.params
/users/:id/users/123{ id: '123' }
/users:id/info/:name/users/123/info/Tim{ id:'123,name:'Tim' }

8.定义NotFound

  • 如果没有匹配到的路由,我们通常会匹配到固定的某个页面
  • 比如在notfound错误页面中,可以写一个动态路由用于匹配所有页面
{
  //如果最后还加了*说明会以 数组来进行解析[/],以/分割
  path:"/:pathMatch(.*)*",
  component:() => import("../pages/NotFound.vue")
}

<template>
  <div>
    <h2>Page Not Found</h2>
    <p>404~</p>
    <!-- 过 $route.params.pathMatch获取到传入的参数 -->
    <h1>{{$route.params.pathMatch}}</h1>
  </div>
</template><script>
  export default {
    
  }
</script><style scoped></style>

9.路由嵌套

  • 路由嵌套其实就是匹配到的页面中也有组件进行切换,那么就可以理解为嵌套,套娃呗

    • 这个时候就也要跟App.vue中进行进行占位
{
    path:"/home",
    name:"home"
    component:()=> import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue"),
    meta: {
      name:"Tim",
      age:21,
      height:1.88
    },
    //这个children就是home嵌套的路由
    children: [
      {
        path:"message",
        component:()=> import("../pages/HomeMessage.vue")
      },
       {
        path:"shops",
        component:()=> import("../pages/HomeShops.vue")
      }
    ]
  }

[⚠️​]  请记得配置完嵌套路由之后要使用占位符进行占位才能显示对应的页面


10.编程式导航

直接上代码,这个没什么好说的⛵️

<template>
  <button @click="gotoHome">去首页</button>
</template>
<script>
  //获取提供的API
  import {useRouter} from "vue-router"
  export default {
    setup() {
      const gotoHome = () => {
        const router = useRouter()
        //router.push("/home")
        //传入的是对象
        router.push({
          path:"/home",
          query:{
            name:"tim",
            age:21
          }
        })
      }
    }
  }
</script>
  • Home.vue
<template>
  <div>
    <h2>Home: {{$route.query.name}}-{{$route.query.age}}</h2>
  </div>
</template>

11.router-link之v-slot

如何使用v-slot?

  • 使用v-slot来作用域插槽来获取内部传给我们的值
属性解释
href解析后的 URL
route解析后的规范化的route对象
navigate触发导航的函数
isActive是否匹配的状态
isExactActive是否是精准匹配的状态
  • App.vue
<template>
  <!-- custom完全自定义,a元素的默认会被取消 -->
  <router-link to="/home" v-slot="props" custom>
    <!-- 以前是用tag="元素",现在就直接在router-link里面放入标签元素及内容在页面就能显示,但是还是a包裹着strong,也能使用组件
    <strong>首页</strong>
    <NavBar title="我是title"/>
    -->
    <button>{{props.href}}</button>
    <!--<p>{{props.route}}</p>-->
    <button @click="props.navigate">{{props.href}}</button>
    <button @click="props.navigate">哈哈哈</button>
    <span :class="{'active': props.isActive}">{{props.isActive}}</span>
    <span :class="{'active': props.isActive}">{{props.isExactActive}}</span>
  </router-link>
</template><script>
  import NavBar from './components/NavBar.vue'
  export default {
    components: {
      NavBar
    },
  }
</script>
<style scoped></style>
  • NavBar.vue
<template>
  <div>
    <h2>{{title}}</h2>
  </div>
</template><script>
  export default {
    props: {
      title: String
    }
  }
</script><style scoped>
/*  */
</style>

12.router-view之v-slot

  • 也提供了一个插槽,可以用于和组件来包裹路由组件

    • Component:渲染的组件
    • route:解析出的标准化路由对象

例子如下,App.vue

<template>
  <router-view v-slot="props">
    <transition name="fade">
      <!-- 进行组件的切换 -->
      <component :is="props.Component"></component>
    </transition>
  </router-view>
</template>
<script>
  export default {
    
  }
</script>
<style scoped>
  .fade-enter-from,
  .fade-leave-to {
    opacity:0;
  }
  
  .fade-enter-from,
  .fade-leave-to {
    opacity:1;
  }
  
  .fade-enter-active,
  .fade-leave-active{
    transition: all 1s ease
  }
</style>

13.动态添加路由

  • 有些情况下可能需要动态添加路由

    • 比如根据用户不同权限,注册不同的路由
    • 这时可以使用addRoute方法
const router = createRouter({
  routes,
  history: createWebHistory()
})
// 动态添加路由
const categoryRoute = {
  path:"/category",
  component:()=> import("../pages/Category.vue")
}
//动态添加二级路由
const Moment = {
  path:"/moment",
  component:()=> import("../pages/HomeMoment.vue")
}
​
router.addRout(categoryRoute)
router.addRoute("home",)
<router-link to="/category">分类</router-link>
<router-link to="/home/moment">动态moment</router-link>

14.动态删除路由

删除路由有三种方式

  • 方式一: 添加一个name想同的路由

  • 方式二:通过removeRoute方法,传入路由的名称

  • 方式三:通过addRoute方法的返回值回调

路由补充API

  • router.hasRoute(): 检查路由是否已存在
  • router.getRoutes(): 获取一个包含所有路由记录的数组

15.路由导航守卫

  • vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。

  • 全局的前置守卫beforeEach是在导航触发时会被回调的:

  • 它有两个参数:

    • to:即将进入的路由Route对象;
    • from:即将离开的路由Route对象;

它有返回值:

  • false:取消当前导航;

  • 不返回或者undefined:进行默认导航;

    • 返回一个路由地址:
    • 可以是一个string类型的路径;
    • 可以是一个对象,对象中包含path、query、params等信息;
  • 可选的第三个参数:next

    • 在Vue2中我们是通过next函数来决定如何进行跳转的;
    • 但是在Vue3中我们是通过返回值来控制的,不再推荐使用next函数,这是因为开发中很容易调用多次next;
router.beforeEach((to,from)=> {
  if(to.path.indexOf("/home") !== -1) {
    return "/login"
  }
})

登陆守卫

if(to.path !== "/login") {
  const token = window.localStorage.getItem("token")
  if(!token) {
    return "/login"
  }
}

Login.vue

<template>
  <button @click="loginClick">
    点击登录
  </button>
</template>
<script>
  import {useRouter} from "vue-router"
  export default {
    setup() {
      const router = useRouter()
      const loginClick = () => {
        window.localStorage.setItem("token","Tim")
        router.push({
          path:"/home"
        })
      }
      
      return {
        loginClick
      }
    }
  }
</script>
<style></style>
  • 下面👇有完整的路由导航解析工作原理,请理解🦄(vue-router文档也有)