Vue Router学习笔记(版本:4.2.4)

Vue Router(版本:4.2.4)

一.简介

官方:Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

vue是单页应用不会有那么多html 让我们跳转 所有要使用路由做页面的跳转

Vue路由允许我们通过不同的 URL 访问不同的内容。通过 Vue 可以实现多视图的单页Web应用

二.快速开始

npm init vue@latest
//或者
npm init vite@latest
...
npm install vue-router@4

Tips:使用 Vue3 安装对应的router4版本;使用 Vue2 安装对应的router3版本

  1. 在src目录下面新建router文件 然后在router文件夹下面新建index.ts,写入如下内容。同时新建components文件夹,新建两个组件login.vue和reg.vue用于演示跳转
// 1. 定义路由组件.
import { createRouter,createWebHashHistory,RouteRecordRaw } from "vue-router";


// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [
    {
        path: "/",
        component: ()=>import('../components/login.vue')
    },{
        path: "/reg",
        component: ()=>import('../components/reg.vue')
    }
];

// 3. 创建路由实例并传递 `routes` 配置
const router  = createRouter({
    // 4. 内部提供了 history 模式的实现。
    history: createWebHashHistory(),
    routes
});

//导出router
export default router;
  1. 在 main.js 中挂载router组件
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//引入router
import router from './router'

createApp(App)
//使用router
.use(router)
.mount('#app')
  1. 在App.vue中写入
<template>
  <div>
    <h1>哈哈哈</h1>
    	<hr>
    <!--使用 router-link 组件进行导航 -->
    <!--通过传递 `to` 来指定链接 -->
    <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
    <RouterLink to="/">login</RouterLink>
    	<hr>
    <RouterLink to="/reg">reg</RouterLink>
    	<hr>
    <!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
    <RouterView></RouterView>
  </div>
</template>
<script setup lang="ts"></script>
<style></style>

在页面中就可以看到呈现的效果

三.标签

请注意,我们没有使用常规的 a 标签,而是使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。我们将在后面看到如何从这些功能中获益。

router-view

router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。

四.基础

前置内容

useRouter()

返回路由器实例:相当于路由的管理者,是一个全局的对象。

useRoute()

返回当前路由对象:一条具体的路由,每一个路由都会有一个route对象。

4.1 路由模式

createWebHashHistory

使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载,其显示的网路路径中会有 “#” 号,有一点点丑。这是最安全的模式,因为他兼容所有的浏览器和服务器。

createWebHistory

美化后的hash模式,会去掉路径中的 “#”。依赖于Html5 的history,pushState API,所以要担心IE9以及一下的版本,感觉不用担心。并且还包括back、forward、go三个方法,对应浏览器的前进,后退,跳转操作。就是浏览器左上角的前进、后退等按钮进行的操作。

createMemoryHistory

适用于所有JavaScript环境,例如服务器端使用Node.js。如果没有浏览器API,路由器将自动被强制进入此模式。

创建一个基于内存的历史。该历史的主要目的是为了处理服务端渲染。它从一个不存在的特殊位置开始。用户可以通过调用 router.pushrouter.replace 将该位置替换成起始位置。

4.1 命名路由

除了path之外,你还可以为任何路由提供name。这有以下优点:

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 url 中出现打字错误。
  • 绕过路径排序(如显示一个)
const routes:Array<RouteRecordRaw> = [
    {
        path:"/",
        name:"Login",
        component:()=> import('../components/login.vue')
    },
    {
        path:"/reg",
        name:"Reg",
        component:()=> import('../components/reg.vue')
    }
]

router-link跳转方式需要改变 变为对象并且有对应name

<h1>hahah</h1>
<div>
  <router-link :to="{name:'Login'}">Login</router-link>
  <router-link :to="{name:'Reg'}">Reg</router-link>
  <hr>
  <a href="/reg">reg_a</a>
  <hr>
  <RouterView></RouterView>
</div>
<hr />

同时上面用a标签的形式也可以完成跳转,只不过a标签在跳转的时候会刷新页面,所以基本不用。

Tips:这里 routerlink中的to一定要变成:to对象的形式,不加这个冒号就是字符串的形式

4.2 编程式导航

除了使用 <router-link> 创建 a标签 来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

  • 模板
<template>
  <div>
    <h1>哈哈哈</h1>
    <hr>
    <RouterLink :to="{name:'logstr'}">logsstr</RouterLink>
    <hr>
    <RouterLink :to="{name:'regstr'}">regstr</RouterLink>
    <hr>
    <a href="/">login_a</a>
    <hr>
    <a href="/reg">reg_a</a>

    <hr>
    <button @click="toPage('/')">按钮1</button>
    <hr>
    <button @click="toPage('/reg')">按钮2</button>
    <hr>
    <RouterView></RouterView>
  </div>
</template>
① 字符串模式
import { useRouter } from 'vue-router'
const router = useRouter()

const toPage = () => {
  router.push('/reg')
}
② 对象模式
import { useRouter } from 'vue-router'
const router = useRouter()

const toPage = () => {
  router.push({
    path: '/reg'
  })
}
③ 命名式路由模式
import { useRouter } from 'vue-router'
const router = useRouter()

const toPage = () => {
  router.push({
    name: 'Reg'
  })
}
④ a标签跳转

直接通过a href也可以跳转但是会刷新页面

import { useRouter } from 'vue-router'
const router = useRouter()

const toPage = () => {
  router.push({
    name: 'Reg'
  })
}

4.3 历史记录

采用replace进行页面的跳转会同样也会创建渲染新的Vue组件但是在history中其不会重复保存记录,而是替换原有的vue组件

<template>
  <div>
    <RouterLink replace :to="{name:'logstr'}">logsstr</RouterLink>
    <RouterLink replace :to="{name:'regstr'}">regstr</RouterLink>
    
    <button @click="toPage('/')">按钮1</button>
    <button @click="toPage('/reg')">按钮2</button>
    
    //横跨历史
    <button @click="next">前进</button>
 		<button @click="prev">后退</button>
    
    <RouterView></RouterView>
  </div>
</template>

在编程时导航中使用router.replace实现不记录历史记录

import { useRouter } from 'vue-router'
const router = useRouter()
 
const toPage = (url: string) => {
  router.replace(url)
}

横跨历史:router.gorouter.back可以实现在历史堆栈中前进或后退多少步

const next = () => {
  //前进 数量不限于1
  router.go(1)
}
 
const prev = () => {
  //后退
  router.back()
}

4.4 路由传参

① Query

传递

<template>
  <div>
    <button @click="toPage('/')">按钮1</button>
    <hr>
    <RouterView></RouterView>
  </div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter()
const item = {
  name: "啊啊啊", 
  age: 99, 
  type: "true"
}

const toPage = (url:string)  => {
  router.push({
    path: url,
    query: item
  })
}
</script>

接收

<template>
    <div>
      <h1>login</h1>
      <div>
        name: {{ route.query.name }}
        age: {{ route.query.age }}
        type: {{ route.query.type }}
      </div>
    </div>
  </template>
  
  <script setup lang="ts">
  import { useRoute } from 'vue-router';
  const route =  useRoute()
  </script>

这种传参方式类似GET请求类型,参数会写在地址栏上

Tips:编程式导航使用router push或者replace的时query必须传入一个对象,它只接受对象。

❓❓❓:网上资料说使用query方式进行传参必须使用路由的path不能使用name,但是我试了一下好像都行,改版了?

② Params❗️(弃用)

4.1.4 (2022-08-22)版本后,方法会失效

const toDetail = (item: Item) => {
    router.push({
        name: 'Reg',
        params: item
    })
}

接受参数使用 useRoute 的 params

<template>
	<div>品牌:{{ route.params.name }}</div>
	<div>ID:{{ route.params.id }}</div>
</template>
<script setup lang="ts">
	import { useRoute } from 'vue-router';
	const route = useRoute()
</script>

这种传参方式类似POST请求类型,参数会写在地址栏上

Tips:由于params方式进行传参,必须使用路由的name,并且这种方式传递的参数,内容是在内存中,页面刷新数据会丢失。

③ 动态路由传参

在创建路由的时候在路径上拼接参数

...
{
  path: "/:id/:name",
  name: "logstr",
  component: ()=>import('../components/login.vue')
},{
  path: "/reg/:id/:name",
  name: "regstr",
  component: ()=>import('../components/reg.vue')
}
...

然后再调用路由的时候,使用params方式进项调用

const toDetail = (item: Item) => {
    router.push({
        name: 'Reg',
        params:{
      		id: item.ids,
      		name: item.name
    		}
    })
}

Tips

  1. 注意此处一定用name匹配路由,否则无效
  2. 同时params中的参数的Key要和路由中的Key一致才可以
  3. 路由中设置的参数都要赋值,否则控制台报错

接受参数使用 useRoute 的 params

<template>
	<div>品牌:{{ route.params.name }}</div>
	<div>ID:{{ route.params.id }}</div>
</template>
<script setup lang="ts">
	import { useRoute } from 'vue-router';
	const route = useRoute()
</script>

这种情况下,参数还是会展示在地址栏中。

4.5 嵌套路由

在父路由下面加children

    {
        path: "/father",
        name: "fstr",
        component: () => import('../components/father.vue'),
        children: [
            {
                //子路由前不需要加斜杠 
                path: "children1",
                name: "cstr1",
                component: () => import('../components/children1.vue')
            },{
                path: "children2",
                name: "cstr2",
                component: () => import('../components/children2.vue')
            }
        ]
    }

Tips:子路由的路径前面不需要加斜杠

4.6 命名视图

在路由的基础上使用命名视图,也就是通过RouterView标签中的name来匹配路由配置文件中的components中配置的不同组件,其中default是默认的视图,不需要name进行匹配。

模板:

<template>
  <div>
    <h1>
      父路由!!
    </h1>
    <RouterLink :to="{name:'cstr1'}">cstr1</RouterLink>
  <hr>
    <RouterLink :to="{name:'cstr2'}">cstr2</RouterLink>
  <hr>
    <RouterView></RouterView>
    <RouterView name="aaa"></RouterView>
    <RouterView name="bbb"></RouterView>
  </div>
</template>

路由:

    {
        path: "/father",
        name: "fstr",
        component: () => import('../components/father.vue'),
        children: [
            {
                //子路由前不需要加斜杠 
                path: "children1",
                name: "cstr1",
                components: {
                    default: () => import('../components/children1.vue')
                }
            },{
                path: "children2",
                name: "cstr2",
                components: {
                    aaa: () => import('../components/children1.vue'),
                    bbb: () => import('../components/children2.vue')
                }
            }
        ]
    }

Tips:是components不是component,否则不生效

4.7 路由重定向-别名

① 重定向

重写目前的浏览器路径至指定的地址,重定向也是通过 routes 配置来完成

{
        path: "/father",
        name: "fstr",
        //重定向到一个命名的路由
        //redirect: { 
        //    name: 'cstr2' 
        //} ,

        //重定向到一个绝对地址
        //redirect: "/father/children2",

        //重定向到一个方法,动态返回重定向目标:
        redirect: to => {
            //方法接收目标路由作为参数
            //return 重定向的字符串路径/路径对象
            return { 
                path: '/father/children1', 
                query: 
                { 
                    name:"123",
                    age: "456"
                } 
            }
        },


        component: () => import('../components/father.vue'),
        children: [
            {
                //子路由前不需要加斜杠 
                path: "children1",
                name: "cstr1",
                components: {
                    default: () => import('../components/children1.vue')
                }
            },{
                path: "children2",
                name: "cstr2",
                components: {
                    aaa: () => import('../components/children1.vue'),
                    bbb: () => import('../components/children2.vue')
                }
            }
        ]
    }

也可以重定向到相对位置:

官方代码:相对重定向

const routes = [
  {
    // 将总是把/users/123/posts重定向到/users/123/profile。
    path: '/users/:id/posts',
    redirect: to => {
      // 该函数接收目标路由作为参数
      // 相对位置不以`/`开头
      // 或 { path: 'profile'}
      return 'profile'
    },
  },
]

❓❓❓:官网重定向到相对路径的写法我本地有问题,我本地版本是4.2.4,我按照官方的写的时候会定位到绝对地址而非相对。

② 别名

alias 就是给地址换个名,通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 / 开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来

{
        path: "/father",
        name: "fstr",
        alias: ['/f', '/f2'],
        component: () => import('../components/father.vue'),
        children: [
            {
                //子路由前不需要加斜杠 
                path: "children1",
                name: "cstr1",
                // 为这 3 个 URL 呈现 children1 ,也可以吧。father 换成/f
                // - /father/children1
                // - /father/c2
                // - /c1
                alias: ['/c1', 'c2'],
                components: {
                    default: () => import('../components/children1.vue')
                }
            }, {
                path: "children2",
                name: "cstr2",
                components: {
                    aaa: () => import('../components/children1.vue'),
                    bbb: () => import('../components/children2.vue')
                }
            }
        ]
    }

Tips:如果你的路由有参数,请确保在任何绝对别名中包含它们:

4.8 滚动行为

当在页面切换了在切换回来的时候,制滚还在你上次的位置,如果没有滚动条,就会在顶上。可以返回一个 Promise 来延迟滚动。

savedPosition就是他自己记录的之前页面你在哪里,直接返回就行,或者自己定义top都可以。

const router = createRouter({
  history: createWebHistory(),
  routes: constantRoutes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  },
});

五.进阶

5.1 导航守卫

5.1.1全局前置守卫

使用 router.beforeEach 注册一个全局前置守卫:

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

每个守卫方法接收两个参数:

  • to: 即将要进入的目标 (到哪儿去)
  • from: 当前导航正要离开的路由 (从哪儿来)

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: truename: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。
router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }
 })

如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调。

如果什么都没有,undefined 或返回 true则导航是有效的,并调用下一个导航守卫

Tips:在之前的 Vue Router 版本中,也是可以使用 第三个参数 next 的。这是一个常见的错误来源,可以通过 RFC 来消除错误。然而,它仍然是被支持的,这意味着你可以向任何导航守卫传递第三个参数。在这种情况下,确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

//白名单
const whiteList = ['/login', '/register'];

router.beforeEach((to,from,next)=>{
    if (whiteList.includes(to.path)) {
        next()
    } else {
        next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
    }
})

5.1.2全局解析守卫

你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。这里有一个例子,确保用户可以访问自定义 meta 属性 requiresCamera 的路由:

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

5.1.3全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。

它们也反映了 navigation failures 作为第三个参数:

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

//也可以用 navigation failures 作为第三个参数:
router.afterEach((to, from, failure) => {
  if (!failure) sendToAnalytics(to.fullPath)
})

5.2 路由元信息

通过路由记录的 meta 属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据,例如:

  • 权限校验标识。
  • 路由组件的过渡名称。
  • 路由组件持久化缓存 (keep-alive) 的相关配置。
  • 标题名称

我们可以在导航守卫或者是路由对象中访问路由的元信息数据。

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      component: () => import('@/views/Login.vue'),
      meta: {
        title: "登录"
      }
    }
  ]
})

5.3 动态路由

我们一般使用动态路由都是后台会返回一个路由表,前端通过调接口拿到后处理(后端处理路由)

动态路由主要通过两个函数实现。router.addRoute()router.removeRoute()。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push()router.replace() 来手动导航,才能显示该新路由

router.addRoute({
  path: '/children3',
  name: 'cstr3',
  component: ()=>(import('../components/children3.vue'))
})

如果你决定在导航守卫内部添加或删除路由,你不应该调用 router.replace(),而是通过返回新的位置来触发重定向:

router.beforeEach(to => {
  if (!hasNecessaryRoute(to)) {
    router.addRoute(generateRoute(to))
    // 触发重定向
    return to.fullPath
  }
})

上面的例子有两个假设:第一,新添加的路由记录将与 to 位置相匹配,实际上导致与我们试图访问的位置不同。第二,hasNecessaryRoute() 在添加新的路由后返回 false,以避免无限重定向。

因为是在重定向中,所以我们是在替换将要跳转的导航,实际上行为就像之前的例子一样。而在实际场景中,添加路由的行为更有可能发生在导航守卫之外,例如,当一个视图组件挂载时,它会注册新的路由。

posted @ 2023-08-09 15:55  抓哇攻城狮  阅读(352)  评论(0)    收藏  举报