路由

路由

Vue Router

Vue Router 的底层实现主要依赖于 Vue 的响应式系统、插件机制以及对浏览器地址变化的监听。以下是其核心实现原理和工作流程:

1. Vue Router 的初始化

当 Vue Router 被安装到 Vue 应用中时,它通过 Vue.mixin 在 Vue 实例的 beforeCreate 钩子中进行初始化:

1
2
3
4
5
6
7
8
9
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
}
}
});
  • 如果 Vue 实例中包含 router 选项,则初始化路由实例,并将当前路由状态 _route 定义为响应式数据。

2. 路由模式

Vue Router 支持两种模式:

  • Hash 模式:通过监听 hashchange 事件来管理路由变化。
  • History 模式:利用 HTML5 的 history API(如 pushStatepopState)来管理路由状态。

3. 路由匹配与渲染

Vue Router 的核心功能是根据当前 URL 匹配路由规则,并渲染对应的组件:

  • 路由匹配:当 URL 发生变化时,Vue Router 会根据配置的路由规则(routes)解析当前路径,并找到匹配的路由记录。
  • 动态渲染:通过 <router-view> 组件动态渲染匹配的组件。<router-view> 会根据当前路由状态 _route 渲染对应的组件。

4. 监听地址变化

Vue Router 通过监听浏览器地址的变化来触发路由更新:

  • 在 Hash 模式下,监听 hashchange 事件。
  • 在 History 模式下,监听 popState 事件。

5. 核心组件

  • <router-link>:用于创建导航链接,类似于 <a> 标签,但不会触发页面刷新。
  • <router-view>:根据当前路由状态动态渲染匹配的组件。

6. 导航守卫

Vue Router 提供了导航守卫(如 beforeEachafterEach),用于在路由导航发生时进行访问控制,例如登录验证。

7. 自定义实现

在自定义实现中,Vue Router 的核心逻辑包括:

  • 创建路由映射:将路由配置(routes)解析为路由映射表。
  • 监听地址变化:通过绑定事件监听地址变化,并触发路由更新。
  • 动态渲染组件:根据当前路由状态动态渲染对应的组件。

总结

Vue Router 的底层实现主要通过以下步骤完成:

  1. 在 Vue 实例中初始化路由系统。
  2. 根据路由模式监听地址变化。
  3. 解析当前路径,匹配路由规则。
  4. 动态渲染对应的组件。
  5. 提供导航守卫,控制路由导航。
    这种机制使得 Vue Router 能够高效地管理路由状态,并与 Vue 的响应式系统无缝集成。

Hash 路由和 History API

在单页面应用(SPA)中,hash 路由和 History API 是两种不同的前端路由实现方式,它们各有优缺点和适用场景。以下是它们之间的主要区别:

URL 结构

  • Hash 路由
    • 使用井号 # 来分隔 URL 的路径部分,例如 http://example.com/#/path/to/resource
    • URL 中的 # 后面的部分称为 hash fragment,浏览器不会将其发送到服务器,服务器无法感知这部分内容。
  • History API
    • 使用标准的 URL 路径,不包含井号,例如 http://example.com/path/to/resource
    • URL 的路径部分会被发送到服务器,服务器需要配置相应的路由处理逻辑。

浏览器行为

  • Hash 路由
    • 当 URL 的 hash 部分发生变化时,浏览器不会重新加载页面,而是触发 hashchange 事件。
    • 前端应用可以监听 hashchange 事件,根据 hash 的值动态加载不同的内容或视图。
  • History API
    • 使用 history.pushState()history.replaceState() 方法可以在不重新加载页面的情况下修改 URL 的路径部分。
    • 前端应用需要手动监听 popstate 事件来处理浏览器的前进和后退操作。

SEO 友好性

  • Hash 路由
    • 对 SEO 不太友好,因为服务器无法感知到 hash 路由的路径,搜索引擎爬虫在抓取页面时可能无法正确解析和索引 hash 路由的内容。
  • History API
    • 对 SEO 更友好,因为服务器可以感知到 URL 的路径部分,搜索引擎爬虫可以抓取和索引这些路径对应的页面内容。

服务器配置

  • Hash 路由
    • 服务器不需要为 hash 路由的路径提供具体的路由处理逻辑,只需将所有请求重定向到应用的入口页面(如 index.html)。
  • History API
    • 服务器需要配置相应的路由处理逻辑,确保当请求 URL 的路径部分时,能够返回正确的页面内容或重定向到应用的入口页面。

兼容性

  • Hash 路由
    • 兼容性较好,几乎所有现代浏览器都支持 hash 的使用和 hashchange 事件的监听。
  • History API
    • 兼容性相对较差,一些较老的浏览器可能不支持 History API。

使用场景

  • Hash 路由
    • 适用于对 SEO 要求不高的应用,或者在服务器配置较为复杂的情况下使用。
  • History API
    • 适用于对 SEO 要求较高的应用,或者需要更自然的 URL 结构的场景。
      总的来说,选择使用 hash 路由还是 History API 取决于具体的应用需求、SEO 要求以及服务器配置等因素。在现代 Web 开发中,随着服务器端渲染(SSR)和静态站点生成(SSG)等技术的发展,History API 的使用越来越普遍,因为它能够提供更自然的 URL 结构和更好的 SEO 支持。

前端路由和后端路由的区别

1. 定义和作用

  • 后端路由
    • 定义:后端路由是指服务器端处理 HTTP 请求的路径映射。当客户端(如浏览器)发送一个 HTTP 请求到服务器时,后端路由根据请求的 URL 和方法(GET、POST、PUT、DELETE 等)将请求分发到相应的处理函数或控制器。
    • 作用:主要负责处理业务逻辑,如数据库操作、文件处理、业务逻辑计算等,并返回相应的响应数据(如 HTML 页面、JSON 数据等)。
    • 示例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 使用 Express 框架的后端路由示例
      const express = require('express');
      const app = express();
      app.get('/users', (req, res) => {
      // 查询数据库获取用户列表
      res.json({ users: ['User1', 'User2', 'User3'] });
      });
      app.post('/users', (req, res) => {
      // 创建新用户
      res.status(201).json({ message: 'User created' });
      });
      app.listen(3000, () => {
      console.log('Server is running on port 3000');
      });
  • 前端路由
    • 定义:前端路由是指在客户端(如浏览器)中处理页面导航的路径映射。通过 JavaScript 操控浏览器的 History API(如 pushStatereplaceState)或 hash 值的变化,实现页面的无刷新切换。
    • 作用:主要负责页面的导航和视图切换,不涉及后端的业务逻辑处理。前端路由通常用于单页面应用(SPA),通过动态加载组件或模块来更新页面内容。
    • 示例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      // 使用 Vue Router 的前端路由示例
      import Vue from 'vue';
      import VueRouter from 'vue-router';
      import Home from './components/Home.vue';
      import About from './components/About.vue';
      import Users from './components/Users.vue';
      Vue.use(VueRouter);
      const routes = [
      { path: '/', component: Home },
      { path: '/about', component: About },
      { path: '/users', component: Users }
      ];
      const router = new VueRouter({
      mode: 'history', // 使用 history 模式
      routes
      });
      new Vue({
      router,
      render: h => h(App)
      }).$mount('#app');

2. 工作原理

  • 后端路由
    • 工作原理:当客户端发送 HTTP 请求到服务器时,后端路由中间件(如 Express 的 app.useapp.get)根据请求的 URL 和方法匹配到相应的路由处理函数。该处理函数执行业务逻辑,并返回响应数据。
    • 示例
      1
      2
      3
      4
      app.get('/api/users', (req, res) => {
      // 处理 GET 请求,返回用户列表
      res.json({ users: ['User1', 'User2', 'User3'] });
      });
  • 前端路由
    • 工作原理:前端路由通过监听浏览器的 URL 变化(如 hash 值变化或 History API 的状态变化),动态加载相应的组件或模块,更新页面内容。前端路由不涉及服务器的请求和响应,整个过程在客户端完成。
    • 示例
      1
      2
      // 使用 Vue Router 监听 URL 变化
      router.push('/about'); // 导航到 /about 路径

3. 性能和用户体验

  • 后端路由
    • 性能:每次页面导航都会发送一个新的 HTTP 请求到服务器,服务器处理请求并返回新的 HTML 页面或数据。这会导致页面的重新加载,性能相对较低。
    • 用户体验:用户在每次导航时会看到页面的重新加载,可能会有短暂的白屏或闪烁,用户体验较差。
  • 前端路由
    • 性能:页面导航时不会发送新的 HTTP 请求,而是通过 JavaScript 动态更新页面内容,性能较高。
    • 用户体验:页面导航时不会重新加载,用户可以看到平滑的过渡效果,用户体验较好。

4. 适用场景

  • 后端路由
    • 多页面应用(MPA):适用于传统的多页面应用,每个页面都是一个独立的 HTML 文件,每次导航都会重新加载页面。
    • 服务器端渲染(SSR):适用于需要服务器端渲染的应用,如搜索引擎优化(SEO)要求较高的应用。
  • 前端路由
    • 单页面应用(SPA):适用于单页面应用,整个应用只有一个 HTML 文件,通过前端路由动态加载不同的组件或模块,实现页面的无刷新切换。
    • 动态内容:适用于需要动态加载内容的应用,如在线编辑器、仪表盘等。

总结

  • 后端路由:主要负责处理业务逻辑,每次导航都会发送新的 HTTP 请求,性能相对较低,用户体验较差。适用于多页面应用和服务器端渲染。
  • 前端路由:主要负责页面导航和视图切换,通过 JavaScript 动态更新页面内容,性能较高,用户体验较好。适用于单页面应用和动态内容加载。
    选择使用后端路由还是前端路由取决于具体的应用需求和项目类型。对于传统的多页面应用和需要服务器端渲染的应用,后端路由是更好的选择;对于单页面应用和需要动态内容加载的应用,前端路由则更加合适。

路由守卫

使用路由守卫进行权限控制

在 Vue. js 中,路由守卫是实现权限控制的强大工具。通过路由守卫,可以在用户导航到不同页面时执行逻辑判断,从而限制访问某些页面或功能。以下是详细的步骤和示例:

1. 定义路由并添加 meta 属性

首先,在路由配置中为需要权限控制的路由添加 meta 属性,用于存储角色或权限信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// router/index.js
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { auth: false } // 登录页面不需要认证
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { auth: true, roles: ['admin', 'user'], permissions: ['dashboard:view'] } // 需要认证,仅管理员和用户可以访问
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: { auth: true, roles: ['admin'] } // 仅管理员可以访问
}
];

2. 全局前置守卫 beforeEach

使用全局前置守卫 beforeEach 来检查用户的权限。在用户尝试访问某个路由时,根据 meta 属性中的角色和权限信息进行判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from '@/store'; // 假设你使用了 Vuex 来管理状态
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
router.beforeEach((to, from, next) => {
const user = store.state.user; // 从 Vuex 中获取用户信息
const { auth, roles, permissions } = to.meta;
// 检查是否需要认证
if (auth) {
// 检查用户是否登录
if (!user.token) {
next({ path: '/login', query: { redirect: to.fullPath } }); // 未登录,跳转到登录页
return;
}
// 检查角色权限
if (roles && !roles.includes(user.role)) {
next({ path: '/403' }); // 角色不匹配,跳转到403页面
return;
}
// 检查功能权限
if (permissions && !permissions.some(permission => user.permissions.includes(permission))) {
next({ path: '/403' }); // 权限不匹配,跳转到403页面
return;
}
}
next(); // 通过检查,继续导航
});
export default router;

3. 组件内守卫

在某些情况下,你可能需要在组件内部进行更细粒度的权限控制。可以使用组件内的守卫 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/views/Dashboard.vue
<script>
export default {
beforeRouteEnter(to, from, next) {
const user = store.state.user; // 从 Vuex 中获取用户信息
const { permissions } = to.meta;
if (permissions && !permissions.some(permission => user.permissions.includes(permission))) {
next({ path: '/403' }); // 权限不匹配,跳转到403页面
} else {
next(); // 通过检查,继续导航
}
}
};
</script>

4. 使用 Vuex 管理用户状态

为了更好地管理用户状态,建议使用 Vuex。以下是一个简单的 Vuex 状态管理示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
token: localStorage.getItem('token') || null,
role: localStorage.getItem('role') || null,
permissions: JSON.parse(localStorage.getItem('permissions') || '[]')
}
},
mutations: {
SET_USER(state, user) {
state.user = user;
localStorage.setItem('token', user.token);
localStorage.setItem('role', user.role);
localStorage.setItem('permissions', JSON.stringify(user.permissions));
}
},
actions: {
login({ commit }, user) {
commit('SET_USER', user);
},
logout({ commit }) {
commit('SET_USER', { token: null, role: null, permissions: [] });
localStorage.removeItem('token');
localStorage.removeItem('role');
localStorage.removeItem('permissions');
}
}
});

5. 权限更新处理

在用户权限发生变化时,需要更新路由和菜单。可以使用 Vuex 的 actions 来处理权限更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// store/permission.ts
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [] as RouteRecordRaw[],
menus: [] as MenuItem[]
}),
actions: {
async refreshPermissions() {
// 获取最新权限
const newPermissions = await fetchUserPermissions();
// 更新用户状态
const userStore = useUserStore();
userStore.updatePermissions(newPermissions);
// 重新生成路由
const router = useRouter();
const permissionCtrl = new PermissionController(userStore.state);
const routeBuilder = new DynamicRouteBuilder(router, permissionCtrl);
// 清空现有路由
this.routes.forEach(route => {
router.removeRoute(route.name as string);
});
// 添加新路由
const asyncRoutes = await fetchUserRoutes();
this.routes = await routeBuilder.addRoutes(asyncRoutes);
// 重新生成菜单
const menuGenerator = new MenuGenerator(permissionCtrl);
this.menus = menuGenerator.generateMenu(menuConfig);
}
}
});

通过以上步骤,你可以有效地使用 Vue Router 的路由守卫来实现权限控制,确保只有具备相应权限的用户才能访问特定的路由。

route和router的区别

在 Vue Router 中,routerouter 是两个核心但易混淆的概念。它们的区别如下:

1. 定义与作用

route router
当前激活的路由信息对象 Vue Router 的实例(路由管理器)
表示当前页面对应的路由状态(路径、参数、查询等) 负责全局路由控制(跳转、守卫、模式配置等)
只读属性,不可直接修改 提供方法(如 push, replace)操作路由

2. 功能对比

route router
提供当前路由的详细信息
path, params, query, hash, meta
提供路由的操作方法
push(), replace(), go(), back()
被动获取路由状态 主动触发路由跳转或控制流程
例如:
/user/123?name=Johnroute.params.id = '123'
例如:
router.push('/user/456') 跳转到新路由

3. 在组件中的访问方式

Options API (Vue 2/3)

1
2
3
4
// 访问当前路由信息(route)
this.$route.params.id
// 访问路由实例(router)
this.$router.push('/home')

Composition API (Vue 3)

1
2
3
4
5
6
7
import { useRoute, useRouter } from 'vue-router';
const route = useRoute(); // 相当于 this.$route
const router = useRouter(); // 相当于 this.$router
// 获取参数
console.log(route.params.id);
// 跳转路由
router.push('/home');

4. 典型使用场景

route 的用途

  • 获取 URL 参数:route.params.id
  • 读取查询字符串:route.query.search
  • 访问路由元信息:route.meta.requiresAuth
  • 监听路由变化:
    1
    2
    3
    watch(() => route.path, (newPath) => {
    console.log('路径变化了:', newPath);
    });

router 的用途

  • 编程式导航:
    1
    2
    3
    router.push('/login');      // 跳转
    router.replace('/profile'); // 替换当前历史记录
    router.go(-1); // 返回上一页
  • 全局路由守卫:
    1
    2
    3
    router.beforeEach((to, from) => {
    // 权限控制逻辑
    });
  • 动态添加路由:
    1
    router.addRoute({ path: '/admin', component: Admin });

5. 类比理解

类比对象 route router
导航系统 当前车辆的位置、速度、方向 导航仪(规划路线、切换路径)
HTTP 请求 请求的 URL 和参数(如 /api/user?id=1 发送请求的客户端(如 axios

6. 常见错误

  • 误用 router.params
    router.params.id不存在,应使用 route.params.id
  • 混淆声明式与编程式导航:
    <router-link :to="router.push(...)"> → 应直接传 to 属性。

总结

  • route = 当前路由的“数据”(只读,反映当前状态)
  • router = 路由的“控制器”(可操作,改变路由状态)
    掌握两者的区别,是高效使用 Vue Router 的关键!