路由的概念

SPA:

  • SPA (Single Page Application,单页面应用),整个应用只有一个完整的页面,所有组件的展示、隐藏都在这一个页面中完成。
  • 不同组件之间的切换需要通过前端路由来实现,数据需要通过 Ajax 获取。
  • 缺点:1. 首屏加载速度较慢、 2. 不利于 SEO。对应的优化方法:1. 路由懒加载、代码压缩、CDN 加速、网络传输压缩、 2. SSR 服务器端渲染。

路由:

  1. 后端路由:请求方式请求地址function 处理函数之间的映射。根据请求的不同,执行不同的 function 对数据进行操作。
  2. 前端路由:hash 地址组件之间的映射。根据 hash 地址的改变,显示、隐藏对应的组件。
  • hash 地址可使用 location.hash 获取。 (eg: http://192.168.1.103:8080/?name=superman#/about#/about)
  • hash 地址也叫锚链接,锚链接的更新不会引起页面的刷新,但会引起历史记录的变化。



Vue Router

  • Vue Router 是 Vue 官方配套的插件,用于实现 SPA
  • 下载时需要注意版本问题:Vue Router 4 - Vue 3、 Vue Router 3 - Vue 2

基本使用

  1. 下载 npm 包:npm i vue-router

  2. 创建 router 配置文件 @/router/index

import Vue from 'vue';
import VueRouter from 'vue-router';

// 注册 vue-router (需在 router 实例创建之前调用)
Vue.use(VueRouter);

// 引入首页组件
import HomeView from '../views/HomeView.vue';

// 配置路由规则
const routes = [
    {
        path: '/', // 路由路径;    注意: hash 模式下, 路由路径要使用小写
        name: 'home', // 路由名称;  可选;  name 值必须保持唯一性
        component: HomeView, // 展示的组件
    },
    {
        path: '/about',
        component: () => import('../views/AboutView.vue'), // 懒加载写法, 访问该组件时再获取
        // 一般情况下, 除初始页面以外的其他页面都应该使用懒加载
    },
];

// 创建 router 实例
const router = new VueRouter({ routes });

// 导出 router 实例
export default router;
  1. 在入口文件 main.js 中导入 router 实例并注册
import Vue from 'vue';
import App from './App.vue';
import router from './router'; // 导入 router 实例
// 只需导入 router 文件夹,默认就会导入该文件里面的 index.js 文件

new Vue({
    router, // 将 router 实例注册到 Vue 实例中
    render: (h) => h(App),
}).$mount('#app');
  1. 编写路由组件:我们约定 [组件] 放置在 components 文件夹中;[路由组件] 放置在 views / pages 文件夹中。

  2. 在 App.vue 中使用路由:1. 添加路由链接<router-link to="/路由路径"> XXX </router-link>; 2. 添加路由占位符<router-view> </router-view> || <router-view />

<template>
    <div id="app">
        <nav>
            <!-- 设置 [路由链接]、并设置 `to` 属性 -->
            <router-link to="/"> Home </router-link>
            |
            <router-link to="/about"> About </router-link>
            <!-- 点击 router-link 会更新路由路径, 进而渲染对应的组件 -->
        </nav>

        <!-- 设置 [路由占位符], 路由路径匹配到的组件会被渲染到这里 -->
        <router-view />
    </div>
</template>

<script>
export default { name: 'App' };
</script>

  • router-link 的底层其实就是 a 标签,所以可以把 router-link 理解为 a 的升级版。
  • 可以给 router-link 设置 props:
    active-class="XXX":设置路由激活时的 class 名,默认为 router-link-active
    exact-active-class="XXX":设置链接精准激活时的 class 名,默认为 router-link-exact-active

  • 访问当前路由,当前路由组件才会被创建;离开当前路由,当前路由组件会被销毁。
  • 注册路由后,Vue 实例身上会多出 2 个属性 $route & $router
    $route:路由组件的信息对象,每个路由的 $route 都不一样。一般用于获取路由信息,eg:路径、query、params…
    $router VueRouter 的实例化对象,整个应用只有一个 $router。一般用于进行编程式导航,eg:push、replace…

路由重定向

可以通过 redirect 属性配置路由重定向。redirect 的属性值可以为 String、Object、Function:

const router = new VueRouter({
    routes: [
        {
            path: '/',
            redirect: '/user', // redirect 的值为目标路由的路由路径 path
        },
        {
            path: '/user',
            component: User,
        },
    ],
});
        {
            path: '/',
            redirect: { name: 'XXX' }, // redirect.name 的值为目标路由的路由名称 name
            // redirect: { path: '/XXX' }, // redirect.path 的值为目标路由的路由路径 path
        },
        {
            path: '/',
            // 接收目标路由作为参数
            redirect: to => {
                // return '路由路径 /XXX'  /  return { name: '路由名称 XXX' }  /  return { path: '路由名称 /XXX' }
                return { path: '/XXX',  query: { title: to.params.searchText } },
            },
        },

  • 相对重定向:
        {
            path: '/users/:id/posts',
            redirect: 'profile' // 相对位置不以 / 开头
        },

上例表示:从 '/users/:id/posts' 重定向到 '/users/:id/profile'


嵌套路由

  1. 设置路由规则:
const router = new VueRouter({
    routes: [
        {
            path: '/home',
            name: 'Home',
            component: HomeView,
            // 设置 children 属性添加子路由, 属性值为数组, 数组元素为子路由的配置对象
            children: [
                {
                    path: 'son', // 注意:子路由的 path 属性值不需要 `/` 前缀 !!!
                    name: 'Son', // 设置路由名称(可选)
                    component: () => import('../views/SonView.vue'),
                },
            ],
        },
    ],
});
  1. 配置组件:

编写 router-link 时,有 3 种写法:
① 直接在 router-link 标签中设置 to 属性,属性值就是路由路径
② 绑定 to 属性,以对象形式,通过组件的 name 属性值,设置路由路径
③ 绑定 to 属性,以对象形式,通过组件的 path 属性值,设置路由路径

<template>
    <div id="app">
        <div id="nav">
            <!-- 直接写路由路径 -->
            <router-link to="/home"> Home </router-link>

            <!-- 绑定 to 属性,以对象的形式,通过组件的 name 属性,设置路由路径 -->
            <!-- <router-link :to="{ name: 'Home' }"> Home </router-link> -->

            <!-- 绑定 to 属性,以对象的形式,通过组件的 path 属性,设置路由路径 -->
            <!-- <router-link :to="{ path: '/home' }"> Home </router-link> -->
        </div>
        <router-view />
    </div>
</template>

<script>
export default { name: 'App' };
</script>
<template>
    <div>
        <h1>This is an Home page</h1>
        <hr />
        <!-- 注意: 编写路径时,路径要写全!!! -->
        <router-link to="/home/son">Son</router-link>
        <router-view></router-view>
    </div>
</template>

<script>
export default { name: 'HomeView' };
</script>
<template>
    <div>
        <h2>Son.vue</h2>
    </div>
</template>

<script>
export default { name: 'SonView' };
</script>

默认子路由

  • 设置子路由时,如果该子路由的 path 属性值为空字符串,则为 [默认子路由]
  • 设置默认子路由后,当前路由不能设置 name 属性,否则 Vue 会抛出警告:“进入当前路由时,可能尚未加载默认子路由”。
const routes = [
    {
        path: '/home',
        component: HomeView,
        // 设置默认子路由后, 当前路由不能设置 name 属性
        children: [
            {
                path: '', // 子组件的 path 设置为空字符串, 表示默认显示该组件
                name: 'Son',
                component: () => import('../views/SonView.vue'),
            },
        ],
    },
];

别名

  • 可通过 alias 属性设置别名:
const routes = [{ path: '/', component: Homepage, alias: '/home' }];

上例中,访问 //home 都会展示 Homepage

  • 可以给 alias 属性传入数组作为属性名,以设置多个别名:
const routes = [
    {
        path: '/users',
        component: UsersLayout,
        children: [
            { path: '', component: UserList, alias: ['/people', 'list'] },
            // 为这 3 个 URL 呈现 UserList
            // - /users
            // - /users/list
            // - /people
        ],
    },
];
  • 如果你的路由有参数,请确保在任何绝对别名中包含它们:
const routes = [
    {
        path: '/users/:id',
        component: UsersByIdLayout,
        children: [
            { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
            // 为这 3 个 URL 呈现 UserDetails
            // - /users/24
            // - /users/24/profile
            // - /24
        ],
    },
];

捕获所有路由

Vue-Router 3 中:

如果想匹配任意路径,可以使用通配符 *

{
    // 会匹配以 `/user-` 开头的任意路径
    path: "/user-*",
},
{
    // 会匹配所有路径
    path: "*",
},
  • 含有通配符的路由应该配置在最后面
  • { path: '*' } 通常用于展示 404 页面

当使用通配符 * 时,$route.params 内会自动添加一个 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin');
this.$route.params.pathMatch; // 'admin'

// 给出一个路由 { path: '*' }
this.$router.push('/non-existing');
this.$route.params.pathMatch; // '/non-existing'

Vue-Router 4 中:

如果想匹配任意路径,需要配置自定义正则:

{
    path: "/:catchAll(.*)", // 动态路由参数 + catchAll(正则);  正则中的 `.` 为通配符;  `*` 为量词, 表示 0 ~ n 个
    redirect: '/404',
}

如果直接使用 *,会报错:Catch all routes ("*") must now be defined using a param with a custom regexp



数据传递

query

query 用于:父路由给子路由传递数据
① 绑定 to 属性,以 [对象] 的形式设置 query 参数
② 绑定 to 属性,以 [字符串] 的形式设置 query 参数

<template>
    <div>
        <h1>This is an Home page</h1>
        <ul>
            <li v-for="item in arr" :key="item.id">
                <!-- 绑定 to 属性,以 [对象] 的形式设置 query 参数 -->
                <router-link
                    :to="{
                        // 通过组件的 name 属性,设置路由路径
                        name: 'Son',

                        // 通过组件的 path 属性,设置路由路径
                        // path: '/home/son',

                        // 通过 query 属性,传递父组件中的数据
                        query: { way: '对象', id: item.id, name: item.hero },
                    }"
                >
                    {{ item.hero }}
                </router-link>
                |
                <!-- 绑定 to 属性,以 [字符串] 的形式设置 query 参数 -->
                <router-link
                    :to="`/home/son?way=字符串模版&id=${item.id}&name=${item.hero}`"
                >
                    {{ item.hero }}
                </router-link>
            </li>
        </ul>
        <hr />
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    name: 'HomeView',
    data() {
        return {
            arr: [
                { id: '001', hero: '卡莎' },
                { id: '002', hero: '火男' },
                { id: '003', hero: '卡萨' },
            ],
        };
    },
};
</script>

通过 $route 在子路由中获取父路由中传递过来的数据:$route.query.属性值

<template>
    <div class="son">
        <!-- 在子组件中,通过 "$route.query.属性值" 获取父组件中的数据 -->
        <p>way: {{ $route.query.way }}</p>
        <p>id: {{ $route.query.id }}</p>
        <p>name: {{ $route.query.name }}</p>
    </div>
</template>

<script>
export default { name: 'SonView' };
</script>

上例的路由配置:

const routes = [
    {
        path: '/home',
        name: 'Home',
        component: HomeView,
        children: [
            {
                path: 'son',
                name: 'Son',
                component: () => import('../views/SonView.vue'),
            },
        ],
    },
];

params (动态路由参数)

除了通过 query 传参,我们还可以通过 params 传递 [动态路由参数]
此时,接收动态参数的路由配置的 path 属性值应改成如下形式:

const routes = [
    {
        path: '/home',
        name: 'Home',
        component: HomeView,
        children: [
            {
                path: 'son/:way/:id/:name', // 设置动态路由
                name: 'Son',
                component: () => import('../views/SonView.vue'),
            },
        ],
    },
];
  • 父路由通过 [对象] / [字符串] 的形式传参。
  • 注意:通过 params 接收数据时:如果使用 [对象] 写法,必须设置 name 属性 !!!
<template>
    <div>
        <h1>This is an Home page</h1>
        <ul>
            <!-- 遍历所有的数据 -->
            <li v-for="item in arr" :key="item.id">
                <!-- 绑定 to 属性,以 [对象] 的形式设置 params 参数 -->
                <router-link
                    :to="{
                        // 必须设置 name 属性
                        name: 'Son',

                        // 通过 params 属性,传递父组件中的数据
                        params: { way: '对象', id: item.id, name: item.hero },
                    }"
                >
                    {{ item.hero }}
                </router-link>
                |
                <!-- 绑定 to 属性,以 [字符串] 的形式设置 params 参数 -->
                <router-link
                    :to="`/home/son/字符串模版/${item.id}/${item.hero}`"
                >
                    {{ item.hero }}
                </router-link>
            </li>
        </ul>
        <hr />
        <router-view></router-view>
    </div>
</template>

子路由通过 $route.params.属性值 获取动态参数:

<template>
    <div>
        <!-- 在子组件中,通过 "$route.params.属性值" 获取父组件中的数据 -->
        <p>way: {{ $route.params.way }}</p>
        <p>id: {{ $route.params.id }}</p>
        <p>name: {{ $route.params.name }}</p>
    </div>
</template>
  • 在 hash 地址中,/ 后面的参数叫 [路径参数];? 后面的参数叫 [查询参数]。
  • [路径参数] 可通过 this.$route.params 获取; [查询参数] 可通过 this.$route.query 获取。
  • 可通过 this.$route.path 获取 hash 地址 + [路径参数]。 可通过 this.$route.fullPath 获取 hash 地址 + [路径参数] + [查询参数]。

如何设置 params 参数可传可不传?

在动态路由后面添加 ?

path: 'son/:msg?', // 动态路由

此时,如果你传了参数,且参数为空字符串 "",Vue 会抛出警告,说不能传递空字符串。


props

在 router 文件中设置 props 属性,可较方便地在路由中接收参数。


方式 1:属性值为【对象】(额外传递一些数据)

该对象中的 key-value 都会传递给子路由,子路由组件通过 props 接收参数:

const routes = [
    {
        path: '/home',
        name: 'Home',
        component: HomeView,
        children: [
            {
                path: 'son/:way/:id/:name',
                name: 'Son',
                component: () => import('../views/SonView.vue'),
                // 方法 1: props 对象, 传递属性及其属性值
                props: { msg: 'props 中的数据' },
            },
        ],
    },
];
<template>
    <div>
        <p>way: {{ $route.params.way }}</p>
        <p>id: {{ $route.params.id }}</p>
        <p>name: {{ $route.params.name }}</p>

        <!-- 在 template 标签中直接使用 -->
        <p>{{ msg }}</p>
    </div>
</template>

<script>
export default {
    name: 'SonView',
    props: ['msg'], // 在组件中设置 props 属性接收数据
};
</script>

方式 2:属性值为【布尔值】(只能传递 params 参数)

所有的 params 数据都会以 props 形式传递:

// 方法 2: 布尔值,表示父路由的所有 params 数据都通过 props 传递
props: true;
<template>
    <div>
        <!-- 在 template 标签中直接使用 -->
        <p>way: {{ way }}</p>
        <p>id: {{ id }}</p>
        <p>name: {{ name }}</p>
    </div>
</template>

<script>
export default {
    name: 'SonView',
    props: ['way', 'id', 'name'], // 在组件中设置 props 属性接收数据
};
</script>

方式 3:属性值为【函数】 [最常用](可以传递 params、query 参数)

函数返回一个对象,对象的 key-value 都会以 props 的形式传递给组件:

// 方法 3:props 方法
props(route) { // 接收 1 个参数,为 $route 对象
    return {
        id: route.params.id,
        name: route.params.name,
        way: route.params.way,
    }
}

这里可以使用 [解构赋值的连续写法]:

props({ params: { id, name, way } }) {
    return { id, name, way };
},

响应路由参数的变化

当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地使用 watch 侦听 $route 对象:

watch: {
    $route(to, from) {
        // 对路由变化作出响应...
    },
},

或者使用 beforeRouteUpdate 导航守卫:

beforeRouteUpdate(to, from, next) {
    // react to route changes...
    // don't forget to call next()
},

注意:若 Vue Router 跳转前后使用的是同一个组件,Vue 会直接复用,这会导致 URL 更新后页面没有重新渲染。此时可以给 router-link 添加唯一标识 key<router-view :key="$route.fullPath" />



编程式路由

至此,上面的 demo 使用的都是 [声明式导航]。下面介绍的是 [编程式导航]。

import Vue from 'vue';
import VueRouter from 'vue-router';

import Home from '../views/Home.vue';

Vue.use(VueRouter);

const routes = [
    {
        path: '/home',
        name: 'Home',
        component: Home,
        children: [
            {
                path: 'son', // 静态路由
                name: 'Son',
                component: () => import('../views/Son.vue'),
                props(route) {
                    // 接收 $route 对象作为第 1 参数
                    return {
                        // 这里通过 query 获取数据
                        id: route.query.id,
                        name: route.query.name,
                        way: route.query.way,
                    };
                },
            },
        ],
    },
    {
        path: '/about',
        name: 'About',
        component: () => import('../views/About.vue'),
    },
];

const router = new VueRouter({ routes });
export default router;

push & replace

  • this.$router.push("指定路由地址"):跳转到指定路由地址
<template>
    <div id="app">
        <div id="nav">
            <router-link to="/home">Home</router-link> |
            <router-link :to="{ name: 'About' }">About</router-link> |
            <button @click="jump">jump Son.vue</button>
        </div>
        <router-view />
    </div>
</template>

<script>
export default {
    name: 'App',
    methods: {
        jump() {
            this.$router.push('/home/son');
        },
    },
};
</script>

使用 push 跳转,会产生新的历史记录;使用 replace 跳转,会替换当前历史记录,即不会产生新的历史记录。

this.$router.replace('/myself');

也可以给 router-link 设置 replace 属性,实现等效的路由跳转:

<router-link replace to="/myself"> 跳转到... </router-link>

跳转的同时传递数据:

String 形式:

// 传递 query 数据
this.$router.push('/home/son?way=push&id=01&name=superman');
// 传递 params 数据
this.$router.push('/home/son/push/01/superman');

Object 形式:

this.$router.push({
    name: 'Son', // 配置 name 属性
    // 传递 query 数据
    query: { way: 'push', id: '01', name: 'superman' },
    // 传递 params 数据
    params: { way: 'push', id: '01', name: 'superman' },
});

注意:需要传递 params 参数时,必须配置 name 属性,不能只配置 path 属性!!


跳转历史记录

  • this.$router.forward() 前进
  • this.$router.back() 后退
  • this.$router.go(num)num > 0,则前进;num < 0,则回退
<template>
    <div id="app">
        <div id="nav">
            <router-link to="/home">Home</router-link> |
            <router-link :to="{ name: 'About' }">About</router-link> |
            <button @click="jump">jump Son.vue</button> |

            <button @click="forward">前进</button> |
            <button @click="back">后退</button>
        </div>
        <router-view />
    </div>
</template>

<script>
export default {
    name: 'App',
    methods: {
        jump() {
            this.$router.replace({
                name: 'Son',
                query: { way: 'push', id: '01', name: 'superman' },
            });
        },
        forward() {
            this.$router.forward();
        },
        back() {
            this.$router.back();
        },
    },
};
</script>

重复路由的报错

使用编程式导航时,如果重复点击相同的导航,会抛出错误!声明式导航没有这种问题,因为声明式导航的底层源码中已经将该问题解决了~

首先,我们需要知道会造成这种问题的原因:push 方法其实接收 3 个参数 location、resolve、reject,其中 resolve、reject 是回调函数,分别在 push [成功]、[失败] 时调用。

既然我们已经知道了 reject 是 push 失败时调用的方法,那我们手动传入 reject 回调函数,即可解决该问题:

this.$router.push(
    {
        name: 'Search',
        params: { keyword: this.keyword },
        query: { KEYWORD: this.keyword.toUpperCase() },
    },
    () => {},
    () => {},
);

但是,这样治标不治本,岂不是每次写 push 都得传入两个回调函数?好麻烦就是说,所以我们可以直接重写 push 方法:

首先我们需要知道,push 方法不是路由实例 VueRouter 的方法,是其原型对象上的方法。

我们打开 router 文件,在创建路由实例之前,重写 push 方法:

// 保存原 push 方法
const originPush = VueRouter.prototype.push;

// 重写 push 方法
VueRouter.prototype.push = function (location, resolve, reject) {
    if ((resolve, reject)) {
        // if 的连续写法
        originPush.call(this, location, resolve, reject);
    } else {
        originPush.call(
            this,
            location,
            () => {},
            () => {},
        );
    }
};

replace 方法同上:

// 保存原 replace 方法
const originReplace = VueRouter.prototype.replace;

// 重写 replace 方法
VueRouter.prototype.replace = function (location, resolve, reject) {
    if ((resolve, reject)) {
        // if 的连续写法
        originReplace.call(this, location, resolve, reject);
    } else {
        originReplace.call(
            this,
            location,
            () => {},
            () => {},
        );
    }
};



导航守卫

全局导航守卫

全局前置守卫 router.beforeEach((to, from, next) => {})

  1. to - 即将要进入的目标路由对象
  2. from - 正要离开的路由对象
  3. next - 放行函数;不调用则会一直卡在这里,无法执行后面的函数
  • 触发的时间:① 初始化时触发、 ② 路由切换之前触发
  • 当一个导航触发时,全局前置守卫按照创建顺序调用
  • 放行函数 next 有 4 种使用方式:
    1. next()- 放行全部路由
    2. next(指定路由) - 跳转到到 指定路由指定路由 可以为路径 "/xxx"{ path: "/" }{ name: "routerName" };如果为 Object,还可以设置replace: true
    3. next(false) - 取消当前的导航;URL 地址会重置到 from 路由对应的地址
    4. next(Error 实例) - 终止导航,错误会被传递给 router.onError() 注册过的回调
// 全局前置守卫,任何路由进入之前触发
router.beforeEach((to, from, next) => {
    console.log('to', to);
    console.log('from', from);
    next(); // 放行
});

全局后置钩子 router.afterEach((to, from) => {})

  1. to - 即将要进入的目标路由对象
  2. from - 正要离开的路由对象
  • 触发时间:① 初始化时触发、 ② 路由离开后触发
  • 一般用于分析、更改页面标题、声明页面等辅助功能…
// 全局后置守卫,任何路由离开后触发
router.afterEach((to, from) => {
    console.log('to', to);
    console.log('from', from);
    document.title = to.name; // 修改页面标题
});

路由独享守卫

  • 路由独享守卫 beforeEnter: (to, from, next) => {}:在指定路由规则中编写;路由进入之前触发
  • beforeEnter 守卫只有从一个不同的路由导航时才会触发。例如,从 /users/2 进入到 /users/3 时是不会触发的。
const routes = [
    {
        path: '/home',
        name: 'Home',
        component: Home,
        children: [
            {
                path: 'son',
                name: 'Son',
                component: () => import('../views/Son.vue'),
                // 指定路由守卫
                beforeEnter: (to, from, next) => {
                    console.log('to', to);
                    console.log('from', from);
                    next(); // 放行
                },
            },
        ],
    },
    {
        path: '/about',
        name: 'About',
        component: () => import('../views/About.vue'),
    },
];
  • 需要设置多个路由独享守卫 beforeEnter 时,可以将函数数组作为属性值传递进去
function removeQueryParams(to) {
    if (Object.keys(to.query).length) {
        return { path: to.path, query: {}, hash: to.hash };
    }
}

function removeHash(to) {
    if (to.hash) {
        return { path: to.path, query: to.query, hash: '' };
    }
}

const routes = [
    {
        path: '/users/:id',
        component: UserDetails,
        beforeEnter: [removeQueryParams, removeHash],
    },
    {
        path: '/about',
        component: UserDetails,
        beforeEnter: [removeQueryParams],
    },
];

组件内的守卫

  1. beforeRouteEnter(to, from, next) {}:路由进入时触发
  2. beforeRouteLeave(to, from, next) {}:路由离开时触发
  3. beforeRouteUpdate(to, from, next) {}:路由复用时触发
  • 通过路由规则 [进入] / [离开] 该组件时,触发组件守卫
  • 在组件的 script 标签中设置
<script>
export default {
    name: 'Son',
    props: ['way', 'id', 'name'],
    beforeRouteEnter(to, from, next) {
        console.log('路由进入时触发');
        console.log('to', to);
        console.log('from', from);
        next();
    },
    beforeRouteLeave(to, from, next) {
        console.log('路由离开时触发');
        console.log('to', to);
        console.log('from', from);
        next();
    },
    beforeRouteUpdate(to, from, next) {
        console.log('路由组件复用时触发');
        console.log('to', to);
        console.log('from', from);
        next();
    },
};
</script>

beforeRouteEnter 方法中,无法直接获取 this 对象,因为 “路由进入前” 组件尚未被创建。此时,我们可以通过 next 的回调函数,在 beforeRouteEnter 方法中获取 this

beforeRouteEnter(to, from, next) {
    console.log("路由进入之前触发", this); // 这里的 this 为 undefined
    next((vm) => { // 通过 next 的回调函数获取 this
        console.log("vm", vm);
    });
},

注意:beforeRouteEnter 是支持给 next 传递回调的唯一守卫。

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


完整的导航解析流程

  1. 导航被触发
  2. 在失活的组件里:触发组件守卫 beforeRouteLeave(to, from, next) {}
  3. 触发全局前置守卫 router.beforeEach((to, from, next) => {})
  4. 在重用的组件里:触发组件守卫 beforeRouteUpdate(to, from, next) {}
  5. 在路由配置里:触发路由独享守卫 beforeEnter: (to, from, next) => {}
  6. 解析异步路由组件
  7. 在被激活的组件里:触发组件守卫 beforeRouteEnter(to, from, next) {}
  8. 导航被确认
  9. 触发全局后置守卫 router.afterEach((to, from) => {})
  10. 触发 DOM 更新
  11. 在被激活的组件里:触发组件守卫 beforeRouteEnter(to, from, next) {} 中传给 next 的回调函数,[创建好的组件实例] 会作为 [回调函数的参数] 传入



设置缓存

  • 组件不用时,默认会被销毁
  • 可以使用 Vue 内置的 keep-alive 标签包裹路由出口 router-view,此时,在该路由出口显示的所有路由都不会被销毁。
<keep-alive> <router-view /> </keep-alive>
  • 我们可以设置 include="组件名",此时只有指定组件会被缓存。注意:include 的属性值是组件名!!!
<keep-alive include="Home"> <router-view /> </keep-alive>
  • 当需要缓存多个组件时,我们可以绑定 include 属性,并传入一个数组参数:
<keep-alive :include="['Home', 'About']"> <router-view /> </keep-alive>
  • 我们也可以设置 exclude 属性,表示除了指定组件,其他组件都会被缓存。书写格式与 include 一样。
<keep-alive exclude="Home"> <router-view /> </keep-alive>

钩子函数:

  • activated() {} keep-alive 缓存的组件激活时调用
  • deactivated() {} keep-alive 缓存的组件失活时调用
activated() {
    console.log("进入缓存组件");
},
deactivated() {
    console.log("离开缓存组件");
},

缓存机制:

因为 keep-alive 有缓存机制,只有第一次打开该路由时会创建 (beforeCreate、created、beforeMount、mounted、activated),再进入时只会激活 activated



设置滚动

在创建 Router 实例时,我们可以通过 scrollBehavior 方法设置滚动,使页面跳转到新路由时,会滚动到指定位置:

// 创建 VueRouter 实例
const router = new VueRouter({
    // ...
    // 设置滚动行为 scrollBehavior
    scrollBehavior(to, from, savedPosition) {
        // `y: 0` 表示滚动条在最上面
        // `behavior: 'smooth'` 可以让滚动变得流畅
        return { y: 0, behavior: 'smooth' };
    },
});

关于 scrollBehavior 的第 3 个参数 savedPosition

只有当这是一个 popstate 导航时才可用。当 history 对象发生变化时,就会触发 popState 事件。可以通过 popState 的事件活动对象的 state 属性访问当前历史记录的状态对象的拷贝。

会触发 popstate 事件的场景:① 用户点击浏览器的前进、后退按钮;② 代码中调用 history.back()history.forward()history.go();③ a 标签的锚点。

注意:当网页加载时,各浏览器对 popstate 事件是否触发有不同的表现:Chrome 和 Safari 会触发 popstate 事件,而 Firefox 不会。


  • 返回值:{ x: number, y: number } / savedPosition
scrollBehavior(to, from, savedPosition) {
    if (savedPosition){
        // 直接返回 savedPosition,在按下 [后退] / [前进] 按钮时,就会像浏览器的原生表现那样
        return savedPosition;
    } else {
        return { y: 0 };
    }
}

滚动到锚点:

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

延迟滚动:

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



路由元信息

  • 用于将任意信息附加到路由规则上。eg:过渡名称、路由访问权限…
  • 可通过配置 meta 属性来设置路由元信息。可以在导航守卫上访问 meta 属性值。
import Vue from 'vue';
import VueRouter from 'vue-router';

import Home from '../views/Home.vue';

Vue.use(VueRouter);

const routes = [
    {
        path: '/home',
        name: 'Home',
        component: Home,
        meta: { name: 'myHome' }, // 设置 meta 属性
        children: [
            {
                path: 'son',
                name: 'Son',
                component: () => import('../views/Son.vue'),
                props(route) {
                    return {
                        id: route.query.id,
                        name: route.query.name,
                        way: route.query.way,
                    };
                },
                meta: { name: 'mySon' }, // 设置 meta 属性
            },
        ],
    },
    {
        path: '/about',
        name: 'About',
        component: () => import('../views/About.vue'),
        meta: { name: 'myAbout' }, // 设置 meta 属性
    },
];

const router = new VueRouter({ routes });

// 全局后置守卫,路由离开后触发
router.afterEach((to, from) => {
    document.title = to.meta.name; // 获取 meta 中的数据
});

export default router;



mode

hash 模式:

  1. 地址中带着 #
  2. 使用 hash 地址 模拟完整的 URL;当 hash 地址更新时,页面不会重新加载
  3. hash 值不会包含在 HTTP 请求中,即 hash 值不会带给服务器
  4. 如果将地址通过第三方手机 app 分享,若 app 校验严格,则会将地址标记为不合法

history 模式:

const router = new VueRouter({ mode: 'history', routes });
  1. 地址中没有 #
  2. history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面
  3. history 模式下配置 base 选项后,所有的 to attribute 都不需要写 [基路径]
  4. 有兼容问题,需要后台配置支持

解决 history 模式下的兼容问题:

  1. 后端人员一个一个地配置路由
  2. 在服务器上添加一个简单的回退路由。如果 URL 匹配不到任何静态资源,则提供 index.html 页面
  3. 后端人员通过插件 connect-history-api-fallback 设置路由(基于 node.js 的 express)
    npm i connect-history-api-fallback、 ② 配置 connect-history-api-fallback
const express = require('express');
const app = express();
app.listen(3000);

// 配置 connect-history-api-fallback, 否则页面刷新后无法正常显示
const history = require('connect-history-api-fallback');
app.use(history());

app.use(express.static('./public'));



面试题

路由传递参数时(对象写法),path 是否可以结合 params 参数一起使用?

答:路由跳转传参时,对象写法不可以只使用 path 属性,否则不会跳转


如何配置 params 参数可传可不传? 配置好后,如果避免传递空字符串?

答:在 params 参数后面添加 ? 即可;可以使用短路算法避免传递空字符串:<router-link :to="{ name: 'Son', params: { msg: '' || null } }"> router </router-link>


路由组件能不能传递 props 数据?

答:可以;可以是 [布尔值]、[对象]、[函数]


Logo

前往低代码交流专区

更多推荐