vue-router

目录

前言

一、vue-router 是什么?

二、vue-router 的基本概念

1、router、route 和 routes 的区别

2、$route 和 $router 的区别

(1)、$route

(2)、$router

三、两种路由模式及其实现原理

1、hash 路由模式(默认)

(1)、hash 路由模式

(2)、hash 路由模式的实现原理

(3)、hash 路由的弊端

2、history 路由模式

(1)、history 对象

(2)、history 对象的属性

(3)、history 对象的方法

(4)、history 对象的事件——popstate 事件

3、路由的底层原理

四、vue-router 的使用

1、安装 vue-router 并在 Vue 上使用它

2、创建一个路由配置表

3、使用路由配置表创建 vue-router 的实例,并将其挂载到 Vue 实例上

4、提供一个路由占位,动态挂载 URL 到与之匹配的组件

(2)、在路由配置表中提供一个“单纯的路由占位”

五、vue-router 的嵌套路由——子路由

六、vue-router 的编程式导航

1、router.push() 方法

2、router.replace() 方法

3、router.go(n) 方法

4、router.open

七、vue-router 的命名路由与命名视图

1、命名路由

2、命名视图

八、vue-router 的路由重定向和别名

1、路由重定向——redirect

(1)、基本的路由重定向

(2)、用路由重定向解决:多个路由公用同一个组件时不重新渲染该组件的问题

(3)、路由重定向的更多高级用法

2、别名——alias

九、 带参数的动态路由匹配

1、用 name 传递参数

2、通过 router-link 标签中的 to 属性传参

3、在路由配置表中以冒号的形式设置参数(:参数)

4、使用path来匹配路由,然后通过query来传递参数

十、动态路由

十一、vue-router 的路由匹配优先级

1、高级匹配模式

十二、vue-router 的导航守卫

1、全局的守卫

(1)、全局前置守卫——beforeEach()

(2)、全局解析守卫——beforeResolve() ( 2.5.0+)

(3)、全局后置钩子——afterEach()

2、单个路由独享的守卫——beforeEnter

3、组件内的守卫

(1)、渲染组件对应路由前的守卫——beforeRouteEnter()

(2)、更新组件对应路由前的守卫——beforeRouteUpdate() (2.2 新增)

(3)、离开组件对应路由前的守卫——beforeRouteLeave()

4、完整的导航解析流程

5、路由导航守卫的执行顺序

(1)、当点击切换路由时,路由导航守卫的执行顺序

(2)、当路由更新时,路由导航守卫的执行顺序

十三、vue-router 的路由元信息——meta

十四、vue-router 的路由的过渡动效

1、单个路由的过渡

2、基于路由的动态过渡

十五、vue-router 的数据获取

1、导航完成之后获取(推荐)

2、导航完成之前获取

十六、vue-router 的滚动行为

十七、vue-router 的路由懒加载

十八、vu3 的 组合 API 中 使用 vue-router

1、编写路由表

2、在 main 里注册

3、在 setup 里使用 vue-router 的 API

(1)、Hooks

 (2)、路由守卫函数


前言

Vue Router 官网icon-default.png?t=N7T8https://router.vuejs.org/zh/

一、vue-router 是什么?

 vue-router 是一个基于 Vue 的 SPA 路由管理器。用来建立 url 和页面之间的映射关系。

vue-router 的功能:

  • 管理嵌套的路由和视图表
  • 实现模块化的、基于组件的路由配置
  • 支持路由参数、查询、通配符
  • 实现基于 Vue.js 过渡系统的视图过渡效果
  • 支持细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 history模式或 hash 模式,在 IE9 中自动降级
  • 支持自定义的滚动条行为

二、vue-router 的基本概念

1、router、route 和 routes 的区别

  • router:是一个机制,用来管理路由。
  • route:是一条路由。例如:Home按钮  => home内容, 这是一条路由; about按钮 => about 内容, 这是另一条路由。
  • routes:是一组路由,把上面的每一条路由组合起来,形成一个数组。例如:[{home 按钮 =>home内容 }, { about按钮 => about 内容}]。

2、$route 和 $router 的区别

  • $route:当前路由信息对象,包括:path,params,hash,query,fullPath,matched,name 等路由信息参数。
  • $router:挂载到 Vue 实例上的 VueRouter 实例。包括了路由的跳转方法,钩子函数等。

(1)、$route

① 、$route.path

字符串,对应当前路由的路径,总是解析为绝对路径,如 "/order"。

②、 $route.params

一个 key/value 对象,包含了 动态片段 和 全匹配片段。

如果没有路由参数,就是一个空对象。

③、 $route.query

一个 key/value 对象,表示 URL 查询参数。

例如,对于路径 /foo?user=1,则有 $route.query.user为1。

如果没有查询参数,则是个空对象。

④、 $route.hash

当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。

⑤ 、$route.fullPath

完成解析后的 URL,包含查询参数和 hash 的完整路径。

⑥、 $route.matched

数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。

⑦、 $route.name

当前路径名字。

(2)、$router

挂载到 Vue 实例上的 VueRouter 实例的案例(下文“vue-router 的使用”也会完整的讲一下):

import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes/index'
import App from './App.vue'

Vue.config.productionTip = false

Vue.use(VueRouter);

const router = new VueRouter({
  // mode: history,
  routes,
})

new Vue({
  router,// 挂载到 Vue 实例上的 VueRouter 实例
  render: h => h(App),
}).$mount('#app')

$router常见的路由的跳转方法(下文“vue-router 的编程式导航”会细讲):

this.$router.go(-1)//跳转到上一次浏览的页面

this.$router.replace('/menu')//指定跳转的地址
this.$router.replace({name:'menuLink'})//指定跳转路由的名字下

this.$router.push('/menu')//通过push进行跳转
this.$router.push({name:'menuLink'})//通过push进行跳转路由的名字下

三、两种路由模式及其实现原理

vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。

1、hash 路由模式(默认

(1)、hash 路由模式

  • hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,因此改变 hash 不会重新加载页面。
  • 每次改变 # 后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。
const router = new VueRouter({
  routes: [...]
})

(2)、hash 路由模式的实现原理

hash 模式的原理是:通过 onhashchange 事件在 window 对象上监测 hash 值(“#”后面的字符串)的变化。当 hash 值发生改变时,浏览器就会渲染指定 DOM 位置的数据。

  • 必须要把 hashchange 事件添加给 window 对象
  • 当 hash 值发生改变时,页面不会重新加载,浏览器只会渲染指定 DOM 位置的数据。

hashchange 事件的 event 对象新增了两个属性:oldURLnewURL

  • oldURL 和 newURL 分别保存着 参数列表变化“前、后”的完整 URL。
  • 最好使用 location 对象来确定当前参数列表,因为目前支持 oldURL 和 newURL 属性的浏览器只有 Firefox6+、Chrome 和 Opera10.6+。

hashchange 事件的应用:

var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);// 检测浏览器是否支持 hashchange 事件

if(isSupported){
    window.addEventListener("hashchange", function(event){
        console.log(`当前 hash 值${location.hash}`);
        console.log('Hash从:' + event.oldURL + '变成了:' + event.newURL);
    });
}

(3)、hash 路由的弊端

  • hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。 
  • hash 的传参是基于 url 的,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在 url 里放参数,还可以将数据存放在一个特定的对象中。
  • 微信分享页面就会把"#"后边的内容处理掉。

2、history 路由模式

由于 hash 模式会在 url 中自带 #,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'"即可。

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

注意:

使用 history 路由模式以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面,比如:创建 NotFoundComponent.vue 作为 404 页面。

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。更多详情请查阅 Vue 服务端渲染文档。 

  • IE9 及以下版本不兼容 history 路由模式,可使用强制刷新处理。

History 模式的原理是:通过 HTML5 中新增的 pushState() 和 replaceState() 方法修改历史记录。

  • history.pushState() 方法:改变浏览器地址栏中的 URL,将数据 push 进会话历史栈,但不加载新的页面
  • history.replaceState() 方法:改变浏览器地址栏中的 URL,更新历史栈上最新的入口,加载新的页面

(1)、history 对象

Window.history;// 返回当前会话的整个 history 对象。

(2)、history 对象的属性

①、length 属性

返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页。

history.length;// 查看当前历史堆栈中页面的数量

②、state 属性

返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态的方式。

history.state;// 返回当前会话的 history 状态。

(3)、history 对象的方法

①、pushState() 方法

将数据 push 进会话历史栈。但浏览器并不会立即向服务器发送请求,不会加载新的页面。

pushState() 方法接收 3 个参数:状态对象、新状态的标题 和(可选的)相对 URL。

  • 状态对象:应该尽可能提供初始化页面状态所需的各种信息。
  • 新状态的标题:目前还没有完全实现,因此完全可以只传入一个空字符串("")或者一个短标题也可以。
  • 新的 URL:(可选的)如果传入这个参数,浏览器地址栏的地址会更新。
    • 新的 url 与原来的 url 遵循“同源策略”,否则会报错。
    • url可以是绝对路径,也可以是相对路径。例如:当前 url 是 https://www.baidu.com/a/ 时,
      • 若执行 history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/;
      • 若执行 history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/。
history.pushState(state, title, url);

②、replaceState() 方法

更新历史栈上最新的入口。会加载新的页面。

replaceState() 方法接收 2 个参数:状态对象 和 新状态的标题——与 pushState() 方法的前两个参数相同。

history.replaceState(state, title, url)

③、back() 方法

在浏览器历史记录里,前往上一页,,用户可点击浏览器左上角的返回按钮模拟此方法.。等价于 history.go(-1)。

history.back();

④、forward() 方法

在浏览器历史记录里,前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法。 等价于 history.go(1)。

history.forward() // 前进

⑤、go() 方法

通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面。比如:参数为-1的时候为上一页,参数为1的时候为下一页。

当整数参数超出界限时,没有效果,也不会报错。

history.go(1) // 前进一步,-2为后退两步

(4)、history 对象的事件——popstate 事件

popstate 事件用来监听浏览器“前进”和“后退”的事件。当活动历史记录更改时,将触发popstate事件。

  • 如果被激活的历史记录条目是通过对 history.pushState() 的调用创建的,或者受到对 history.replaceState() 的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。
  • 调用 history.pushState() 或 history.replaceState() 不会触发 popstate 事件。只有在做出浏览器动作时,才会触发该事件,例如:
    • 用户点击浏览器的回退按钮;
    • 在 Javascript 代码中调用 history.back() 方法,或者 history.forward() 方法,或者 history.go(1) 方法。
window.addEventListener('popstate', (event) => {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
});

history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");

history.back(); // Logs "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // Logs "location: http://example.com/example.html, state: null
history.go(2);  // Logs "location: http://example.com/example.html?page=3, state: {"page":3}

3、路由的底层原理

通过上图中 5 种方式,都会触发 vue-router 组件里的 updateRoute 钩子函数,该钩子函数会改变 Vue.util.defineReactive_route 里的响应式 route 的信息,然后自动触发 router-view 的视图的更新,router-view 会根据我们提供的 URL 匹配到需要渲染的组件。

四、vue-router 的使用

  • 安装 vue-router 并在 Vue 上使用它。
  • 创建一个路由配置表(也叫 VueRouter 的参数列表)。
  • 使用路由配置表创建 vue-router 的实例,并将其挂载到 Vue 实例上。
  • 使用 <router-view></router-view> 标签提供一个路由占位,动态挂载 URL 到与之匹配的组件。

1、安装 vue-router 并在 Vue 上使用它

安装 vue-router:

npm i vue-router -S

将 vue-router 挂载到 Vue 实例上:

在 main.js 中

import VueRouter from 'vue-router'
Vue.use(VueRouter);

安装完后,想看官方使用教程 请戳这里。 

2、创建一个路由配置表

在项目的 src 目录下,新建 routes 文件夹,来统一管理路由配置表。

然后,我这里新建了一个 index.js 作为我的路由配置表,比如:

// 引入组件
import RouterDemo from "../components/RouterDemo";

// 配置路径与组件的映射
const routes = [
    {
        path: "/",
        redirect: "/bar",
    },
    {
        path: "/bar",
        component: RouterDemo,
        name: "2",
    },
    {
        path: "/user",
        component: RouterDemo,
        name: "3",
        children: [
          {
            path: "profile",
            component: RouterChildrenDemo,
          },
        ],
    }
];

export default routes;

3、使用路由配置表创建 vue-router 的实例,并将其挂载到 Vue 实例上

在 main.js 中,使用路由配置表创建 vue-router 的实例:

import routes from './router'

const router = new VueRouter({
  // mode: history,
  routes,
})

将 vue-router 实例挂载到 Vue 实例上:

new Vue({
  router,// 将 vue-router 实例挂载到 Vue 实例上
  render: h => h(App),
}).$mount('#app')

4、提供一个路由占位,动态挂载 URL 到与之匹配的组件

router-link 是 vue-router 提供的一个全局组件,它是用来做路由跳转,底层其实就是一个 <a></a> 链接(实际渲染出来的标签也是 <a></a> 标签)。

router-view 是 vue-router 提供的一个全局组件,它是用来提供一个路由占位,用来挂载 URL 匹配到的组件。

具体使用,请看下面的案例:

在 RouterDemo.vue 中控制路由的切换,并通过 <router-view></router-view> 标签提供一个路由占位:

// RouterDemo.vue
<template>
    <div>

        <router-link to="/user/profile">Go to /user/profile</router-link>

        <router-view></router-view> 

    </div>
</template>
<script>
export default {
    
};
</script>

【拓展】

使用路由模块来实现页面跳转的方式:

  • 方式1:直接修改地址栏

  • 方式2:this.$router.push(‘路由地址’)

  • 方式3:使用 <router-link></router-link> 标签

(2)、在路由配置表中提供一个“单纯的路由占位”

如果不用 render 函数,你需要写一个单文件组件,而单文件组件的本质就是render函数——单文件组件通过 vue 的 loader 都会转成 render 函数。

在路由配置表中,设置 component 参数时,使用 rander 函数直接渲染一个 router-view 组件:

const routes = [
  {
    path: "/",
    redirect: "/user"
  },
  {
    path: "/user",

    /* 使用 rander 函数直接渲染一个 router-view 组件 */
    component: {render:h => h('router-view')},
    /* 等价于下面这种写法:
    component: {render(h) {
      return h('router-view')
    }}, */

    children:[
      {
        path: "/user",
        redirect: "/user/login"
      },
      {
        path: "login",
        name: "Login",
        component: () =>
          import(/* webpackChunkName: "user" */ "../views/user/Login")
      },
      {
        path: "register",
        name: "Register",
        component: () => 
          import(/* webpackChunkName: "user" */ "../views/user/Register.vue")
      }
    ]
  },
]

因此,就没必须要像下面这样定义一个单文件组件,来使用 router-view 组件,然后引入到 routers 里了:

<template>
  <div>
      <router-view></router-view>
  </div>
</template>

<script>
export default {

}
</script>

【结论】

如果只是需要一个路由占位(router-view 组件)而没有别的内容需要展示的话——我称其为“单纯的路由占位”,完全可以采用 render 函数来加载它。

五、vue-router 的嵌套路由——子路由

一般 App.vue 组件里的 <router-view> 是最顶层的出口,渲染最高级路由匹配到的组件:

App.vue 组件中:

<template>
  <div id="app">
    <h2>router demo</h2>

    <router-view></router-view><!-- 根路由 -->

  </div>
</template>

<script>
export default {
  name: 'app',
}
</script>

同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view>:

RouterDemo.vue 组件中:

<template>
    <div>
        <router-link to="/user/12">Go to /user/12</router-link>
        <br />
        <router-link to="/user/12/profile">Go to /user/12/profile</router-link>
        <br />
        <router-link to="/user/12/posts">Go to /user/12/posts</router-link>
        <br />
        <p>id: {{ id }}</p>

        <router-view></router-view><!-- 子路由 -->

    </div>
</template>
<script>
export default {
    props: ["id"],
};
</script>

RouterChildrenDemo.vue 组件中:

<template>
    <div>
        {{ routerInfo }}
    </div>
</template>
<script>
export default {
    computed: {
        routerInfo() {
            const { fullPath, path, name, params, query, meta } = this.$route;
            return {
                fullPath,
                path,
                name,
                params,
                query,
                meta,
            };
        },
    },
};
</script>

要在嵌套的出口中渲染组件,需要在路由配置表使用 children 配置:

src/routes/index.js 中:

{
  path: "/user/:id",
  component: RouterDemo,
  name: "3",
  props: true,
  children: [
    {
      // 当 /user/:id/profile 匹配成功,
      // RouterChildrenDemo 会被渲染在 RouterDemo 的 <router-view/> 中
      path: "profile",
      component: RouterChildrenDemo,
      name: "3-1",
    },
    {
      // 当 /user/:id/posts 匹配成功
      // RouterChildrenDemo 会被渲染在 RouterDemo 的 <router-view/> 中
      path: "posts",
      component: RouterChildrenDemo,
      name: "3-2",
    },
  ],
},

以“ / ”开头的嵌套路径会被当作根路径。 由上述代码可以看出,在路由配置表中,根路由的 path 属性需要加“/”,比如:path: "/user/:id"。而子路由的 path 属性不需要加“/”,比如:path: "profile"。

六、vue-router 的编程式导航

1、router.push() 方法

会在当前页面打开。

效仿 window.history API 的 window.history.pushState() 方法。

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

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)。

声明式编程式
<router-link :to="...">router.push(...)

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。 

该方法可以接收 3 个参数:

  • 一个路由描述;
  • (可选的)在导航成功完成时执行的回调函数;
  • (可选的)在导航终止时执行的回调函数。

该方法返回一个 Promise。

该方法的第一个参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意:与 router-link 组件的 to 属性一样,如果提供了 path,params 会被忽略。

// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

2、router.replace() 方法

效仿 window.history API 的 window.history.replaceState() 方法。

声明式编程式
<router-link :to="..." replace>router.replace(...)

与 router.push() 方法的不同是:它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

该方法也可以接收 3 个参数,与 router.push() 方法一样。该方法也返回一个 Promise。

3、router.go(n) 方法

效仿 window.history API 的 window.history.go() 方法。

该方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步。

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

注意:如果 history 记录不够用,没有效果,也不会报错。

4、router.open

会打开一个新的浏览器页面。

其他与 router.push() 方法类似。

七、vue-router 的命名路由与命名视图

1、命名路由

在 routes 配置中给某个路由设置名称。

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

这跟代码调用 router.push() 是一样的:

router.push({ name: 'user', params: { userId: 123 }})

2、命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。

你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

八、vue-router 的路由重定向和别名

1、路由重定向——redirect

(1)、基本的路由重定向

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b。

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

(2)、用路由重定向解决:多个路由公用同一个组件时不重新渲染该组件的问题

当多个路由公用同一个组件时,切换路由不会重新触发该组件页面的钩子函数。为了重新触发钩子函数。获取最新数据。可以这样做:

在重定向父路由时,在其子路由列表中进行。

const routes = [
  {
    path: "/user",
    component: () =>
      import(/* webpackChunkName: "layout" */ "../layouts/UserLayout.vue"),
    children: [
      {
        path: "/user",

        /* 重定向父路由时,在其子路由列表中进行 */
        redirect: "/user/login",

      },
      {
        path: "login",
        name: "Login",
        component: () =>
          import(/* webpackChunkName: "user" */ "../views/user/Login"),
      },
      {
        path: "register",
        name: "Register",
        component: () =>
          import(/* webpackChunkName: "user" */ "../views/user/Register.vue"),
      },
    ],
  },
];

【拓展】对于该问题,还可以采用下面两种方法来解决:

方法1:假如父组件下的两个子组件A ,B需要公用同一个组件,在父组件中,router-view中添加key ,并保证key值具有类似ID值的唯一性。 这样就能实现,进行相应的子组件路由,会根据相应的key值触发相应子组件的钩子函数。

<router-view :key="$route.XXX"></router-view>

方法2:通过 watch 监听路由的变化,来管理切换路由。

// 推荐这样写:
watch: {
    '$route.query.id' () {
        // ...
    },
    '$route.query.page' () {
        // ...
    }
}

// 不推荐这样写:
watch: {
    '$router' (to, from) {
        var path = to.path;
        if (path.indexOf("id") != -1){
            // ...
        } else if (path.indexOf("page") != -1){
            // ...
        }
    }
}

(3)、路由重定向的更多高级用法

尤大大给出的案例

2、别名——alias

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

九、 带参数的动态路由匹配

带参数的动态路由匹配 | Vue Router

1、用 name 传递参数

路由配置表中:

routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    }
]

模板里 用$route.name 来接收。比如:

<p>{{ $route.name}}</p>

2、通过 router-link 标签中的 to 属性传参

<router-link :to="{name:xxx,params:{key:value}}">valueString</router-link>

注意:to 属性里,如果提供了 path,params 会被忽略。 

// 这里的 params 不生效
<router-link :to="{
    name:'xxx',
    path:'/xxx',
    params:{key:value}}">valueString</router-link>

3、在路由配置表中以冒号的形式设置参数(:参数)

{
  path: "/user/:id",
  component: RouterDemo,
  name: "3",
},

然后在 RouterDemo.vue 组件中,通过 $route.params.XXX 来使用。比如:

<p>{{ $route.params.id}}</p>

上述代码,是在组件中使用 $route,这会使 $route 与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

解决办法:路由组件传参——使用 props 将组件和路由解耦:

{
  path: "/user/:id",
  component: RouterDemo,
  name: "3",
  props: true,
},

如果 props 属性 被设置为 true,route.params(这里指的是 id)将会被设置为组件属性。

在 RouterDemo.vue 组件中可以直接使用 id 这个属性:

export default {
    props: ["id"],
};

4、使用path来匹配路由,然后通过query来传递参数

<router-link :to="{ name:'Query',query: { queryId:  status }}" >
     router-link跳转Query
</router-link>

路由配置表中:

{
   path: '/query',
   name: 'Query',
   component: Query
}

获取参数:

this.$route.query.queryId

十、动态路由

动态路由 | Vue Router

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

【注意】:在v4中,“router.addRoutes()” 函数已弃用。 

举个例子:

export const constantRouterMap: Array<RouteRecordRaw> = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/login/index.vue')
  },
  {
    path: '/home',
    name: 'Home',
    component: () => import('../views/Home.vue')
  }
]

export const asyncRouterMap: Array<RouteRecordRaw> = [
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  }
]

定义一个静态route的数组和动态route的数组,在通过角色判断的时候,我们把所有的通过对比,实现动态添加路由。

从最上面的route中我们能看到,当前只有home和login页面是可以访问的,about是个动态route,在没有添加进去的时候我们访问。因此我们需要加载进去,我是通过一个点击事件把route加载进去的就是之前页面的点击事件。

<template>
  <h1>这是home页面</h1>
  <button style="color:blue" @click="addRoute">添加route</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import router, { asyncRouterMap } from '@/router/index'
export default defineComponent({
  setup () {
    const addRoute = () => {
      asyncRouterMap.forEach(item => {
        router.addRoute(item)
      })
      router.push('/about')
    }
    return {
      addRoute
    }
  }
})
</script>

点击后,动态route就这么实现了,至于更多的,其实是对路由做比对,在前端做权限管理。

十一、vue-router 的路由匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

1、高级匹配模式

vue-router 使用 path-to-regexp 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。查看它的文档学习高阶的路径匹配,还有这个例子 展示 vue-router 怎么使用这类匹配。

十二、vue-router 的导航守卫

导航守卫是路由跳转过程中的一些钩子函数。

导航守卫的作用:通过 跳转 或 取消 的方式 守卫路由(导航)。

vue-router 提供的导航守卫的方式有 3 种:

  • 全局的守卫:beforeEach、beforeResolve( 2.5.0+)、afterEach。
  • 单个路由独享的守卫:beforeEnter。
  • 组件级的守卫:beforeRouteEnter、beforeRouteUpdate (2.2+)、beforeRouteLeave。

除了 全局后置钩子,其他的守卫方法都接收 3 个参数:to、from 和 next。

  • to: Route: 即将要进入的目标 路由对象。
  • from: Route: 当前导航正要离开的路由。
  • next: Function: 一定要确保 next 函数在任何给定的导航守卫中都被严格调用一次,该方法用来 resolve 这个钩子。执行效果依赖 next 方法的调用参数:
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

1、全局的守卫

参数或查询的改变并不会触发 beforeEach(进入) 和 afterEach(离开) 。



(1)、全局前置守卫——beforeEach()

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})



(2)、全局解析守卫——beforeResolve() ( 2.5.0+)

这和 router.beforeEach() 类似,唯一的区别是:在导航被确认之前,在所有 组件内守卫 和 异步路由组件 被解析之后,解析守卫就会被调用。

const router = new VueRouter({ ... })

router.beforeResolve((to, from, next) => {
  // ...
})



(3)、全局后置钩子——afterEach()

  • 这个钩子不会接受 next 函数也不会改变导航本身。
  • 参数或查询的改变并不会触发 afterEach() 。
router.afterEach((to, from) => {
  // ...
})



2、单个路由独享的守卫——beforeEnter

在路由配置表上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})



3、组件内的守卫

你可以在组件内,像使用Vue的钩子函数那样,直接使用组件内的路由导航守卫。

组件内的路由导航守卫如下:

  • 渲染组件对应路由前的守卫——beforeRouteEnter()。
  • 更新组件对应路由前的守卫——beforeRouteUpdate() (2.2 新增)。
  • 离开组件对应路由前的守卫——beforeRouteLeave()。

(1)、渲染组件对应路由前的守卫——beforeRouteEnter()

  • 在渲染该组件的对应路由时,被 confirm 前调用。
  • 不能获取组件实例 `this`——当守卫执行前,组件实例还没被创建。
    • 不过,你可以通过传一个回调给 next 来访问组件实例。
    • beforeRouteEnter 是支持给 next 函数传递回调的唯一守卫。
beforeRouteEnter (to, from, next) {
  // ...
},

通过传一个回调给 next 来访问组件实例:

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

在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。 

(2)、更新组件对应路由前的守卫——beforeRouteUpdate() (2.2 新增)

  • 路由更新——在当前路由改变 并且该组件被复用时 调用——当前路由 query 参数变更时,该守卫会被调用。比如:
    • 对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候。
    • 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  • 可以访问组件实例 `this`。
beforeRouteUpdate (to, from, next) {
  // ...
},

(3)、离开组件对应路由前的守卫——beforeRouteLeave()

  • 导航离开该组件的对应路由时调用。
  • 可以访问组件实例 `this`。
beforeRouteLeave (to, from, next) {
  // ...
}

该导航可以通过 next(false) 来取消:

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

4、完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

注意:守卫是异步解析执行,导航在所有守卫resolve 完之前一直处于等待中。

5、路由导航守卫的执行顺序

(1)、当点击切换路由时,路由导航守卫的执行顺序

离开的路由 { 
    beforeRouterLeave(离开组件对应路由前的守卫)
} -->

进入的路由 { 

    beforeEach(全局前置守卫) -->

    beforeEnter(单个路由独享的守卫)-->

    beforeRouteEnter(渲染组件对应路由前的守卫)-->

    beforeResolve(全局解析守卫)-->

    afterEach(全局后置钩子)-->

    Vue 的生命周期 -->

    beforeRouteEnter 的 next 的回调(渲染组件对应路由前的守卫的 next 的回调)

}

(2)、当路由更新时,路由导航守卫的执行顺序

组件路由更新(当前路由 query 参数变更):beforeRouteUpdate(更新组件对应路由前的守卫)

十三、vue-router 的路由元信息——meta

定义路由的时候可以配置 meta 字段:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    //此路由需要身份验证,请检查是否已登录
    //否则,重定向到登录页面。
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

十四、vue-router 的路由的过渡动效

路由的过渡:切换路由时,上一个路由离开,下一个路由进入,整个过程就是路由的过渡。

使用 <transition> 组件给 <router-view>  动态组件添加一些过渡效果:

<transition>
  <router-view></router-view>
</transition>

Transition 的所有功能

1、单个路由的过渡

如果你想让每个路由组件有各自的过渡效果,可以通过给各路由组件内使用的 <transition> 设置不同的 name 来实现单个路由的过渡。

<transition name="transitionOne">
  <router-view></router-view>
</transition>

<transition name="transitionTwo">
  <router-view></router-view>
</transition>

2、基于路由的动态过渡

在单个路由过度的基础上,用 v-bind 来实现基于路由的动态过渡。

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
  '$route' (to, from) {
    const toDepth = to.path.split('/').length
    const fromDepth = from.path.split('/').length
    this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
}

十五、vue-router 的数据获取

vue-router 的数据获取的两种思路:

  • 导航完成之后获取(推荐):先完成导航,然后在接下来的组件生命周期钩子(created)中获取数据。在数据获取期间显示“加载中”之类的指示。

  • 导航完成之前获取:导航完成前,在路由进入(beforeRouteEnter)的守卫中获取数据,在数据获取成功后执行导航。

1、导航完成之后获取(推荐)

先完成导航,然后在接下来的组件生命周期钩子(created)中获取数据。在数据获取期间显示“加载中”之类的指示。

假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

<template>
  <div class="post">
    <div v-if="loading" class="loading">
      Loading...
    </div>

    <div v-if="error" class="error">
      {{ error }}
    </div>

    <div v-if="post" class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {
    // 组件创建完后获取数据,
    // 此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
}

2、导航完成之前获取

导航完成前,在路由进入(beforeRouteEnter)的守卫中获取数据,在数据获取成功后调用 next 方法来执行导航。

export default {
  data () {
    return {
      post: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了
  // 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

十六、vue-router 的滚动行为

需求:使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。

方案:vue-router 提供了一个方法 scrollBehavior 方法来满足此需求——该方案只在支持 history.pushState() 的浏览器中可用——路由必须采用 history 模式了

scrollBehavior 方法接收 3 个参数:to、from 和 一个函数,比如 savedPosition() 函数。

  • to 和 from 都是路由对象。
  • 第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})

对于所有路由导航,简单地让页面滚动到顶部。并返回 savedPosition() 函数:

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

如果你要模拟“滚动到锚点”的行为:

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

你也可以返回一个 Promise 来得出预期的位置描述——异步滚动:

scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}

十七、vue-router 的路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

  • 将异步组件定义为返回一个 Promise 的工厂函数
  • 在 Webpack 2 中,我们可以使用动态 import语法来定义代码分块点 。

注意:如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

const Foo = () => import('./Foo.vue')

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

把组件按组分块

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。

十八、vu3 的 组合 API 中 使用 vue-router

安装方式没变。

1、编写路由表

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/pages/home/index.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/pages/about/index.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

2、在 main 里注册

import { createApp } from 'vue'
import App from './App.vue'
import router from '@/route/routes'

const app = createApp(App)

app.use(router)

app.mount('#app')

3、在 setup 里使用 vue-router 的 API

API 参考 | Vue Router composition-apiVue.js 的官方路由icon-default.png?t=N7T8https://router.vuejs.org/zh/api/#composition-api

(1)、Hooks

自 vue3 推广以来,vue-router v4 增加了一些支持 vue3 composition API 的 hooks:

  • useRouter:等价于之前的 this.$router 。
  • useRoute:等价于之前的 this.$route 。
import { useRouter, useRoute } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()

    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }
  },
}
  • useLink:Vue Router 将 RouterLink 的内部行为作为一个组合式 API 函数公开。它提供了与 v-slot API 相同的访问属性。
import { RouterLink, useLink } from 'vue-router'
import { computed } from 'vue'

export default {
  name: 'AppLink',

  props: {
    // 如果使用 TypeScript,请添加 @ts-ignore
    ...RouterLink.props,
    inactiveClass: String,
  },

  setup(props) {
    const { route, href, isActive, isExactActive, navigate } = useLink(props)

    const isExternalLink = computed(
      () => typeof props.to === 'string' && props.to.startsWith('http')
    )

    return { isExternalLink, href, navigate, isActive }
  },
}

 (2)、路由守卫函数

组合式 API 里的路由守卫函数:

  • onBeforeRouteLeave:等价于原来的 beforeRouteLeave 路由守卫钩子函数。
  • onBeforeRouteUpdate:等价于原来的 beforeRouteUpdate 路由守卫钩子函数。
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
  setup() {
    // 与 beforeRouteLeave 相同,无法访问 `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // 取消导航并停留在同一页面上
      if (!answer) return false
    })

    const userData = ref()

    // 与 beforeRouteUpdate 相同,无法访问 `this`
    onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
  },
}

【参考资料】:

vue-router 官网:Vue Router

从头开始学习vue-router:从头开始学习vue-router - 简书

遇到面试vue-router原理:遇到面试vue-router原理_凌晨四点半er的博客-CSDN博客_vue-router原理

浅谈vue-router原理:浅谈vue-router原理 - 简书

  • 2
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值