前端2

75 阅读19分钟

微前端不同系统之前如何共享状态?

微前端架构中,不同子系统(应用)通常是独立开发、部署的,但在实际场景中常需要共享状态(如用户信息、全局配置、权限数据等)。由于子系统可能使用不同的技术栈(Vue、React、Angular 等),共享状态需要兼顾跨框架兼容性安全性低耦合性。以下是常用的实现方案及原理:

一、基于全局变量(Window)共享

这是最简单直接的方案,通过浏览器的全局对象 window 存储共享状态,所有子应用都可访问。

实现方式:
  1. 主应用定义全局状态:在主应用中初始化共享状态,并挂载到 window 上。

    javascript

    运行

    // 主应用
    window.sharedState = {
      userInfo: { id: 1, name: '张三' },
      theme: 'light',
      setUserInfo: (info) => {
        window.sharedState.userInfo = info;
        // 触发全局事件通知状态变化
        window.dispatchEvent(new CustomEvent('userInfoChange', { detail: info }));
      }
    };
    
  2. 子应用读取 / 修改状态:子应用通过 window.sharedState 访问状态,修改时可触发全局事件通知其他应用。

    javascript

    运行

    // Vue 子应用
    export default {
      mounted() {
        // 读取全局状态
        this.userInfo = window.sharedState.userInfo;
        // 监听状态变化
        window.addEventListener('userInfoChange', (e) => {
          this.userInfo = e.detail;
        });
      },
      methods: {
        updateUser() {
          // 修改全局状态
          window.sharedState.setUserInfo({ id: 1, name: '李四' });
        }
      }
    };
    
优缺点:
  • 优点:实现简单,跨框架兼容(任何技术栈都能访问 window)。

  • 缺点

    • 全局变量污染,可能存在命名冲突。
    • 状态修改缺乏管控,容易引发不可预期的副作用。
    • 不支持状态变更的细粒度监听(需手动实现事件机制)。
适用场景:

简单场景(如共享用户信息、基础配置),子应用数量少且信任度高。

二、基于自定义事件(EventBus)通信

通过浏览器的 CustomEvent 机制实现状态共享,主应用作为事件中枢,子应用通过 “发布 - 订阅” 模式传递状态。

实现方式:
  1. 主应用提供事件总线:封装事件监听和触发方法,避免直接操作 window

    javascript

    运行

    // 主应用:event-bus.js
    const eventBus = {
      on: (name, callback) => window.addEventListener(name, callback),
      off: (name, callback) => window.removeEventListener(name, callback),
      emit: (name, data) => window.dispatchEvent(new CustomEvent(name, { detail: data }))
    };
    window.eventBus = eventBus;
    
  2. 子应用通过事件共享状态

    • 子应用 A 发布状态变更事件:

      javascript

      运行

      // 子应用 A(React)
      function updateTheme(theme) {
        window.eventBus.emit('themeChange', theme); // 发布事件
      }
      
    • 子应用 B 订阅事件获取状态:

      javascript

      运行

      // 子应用 B(Vue)
      mounted() {
        window.eventBus.on('themeChange', (e) => {
          this.theme = e.detail; // 订阅事件,更新本地状态
        });
      },
      beforeDestroy() {
        window.eventBus.off('themeChange'); // 解绑事件,避免内存泄漏
      }
      
优缺点:
  • 优点:解耦性好(子应用无需知道彼此存在),跨框架兼容。

  • 缺点

    • 状态是 “一次性传递”,新挂载的子应用无法获取历史状态(需额外处理初始化)。
    • 复杂状态管理困难(如状态依赖、回溯)。
适用场景:

简单的跨应用通信(如主题切换、通知提示),状态变更频率低。

三、基于全局状态管理库(跨框架)

使用支持跨框架的状态管理库(如 ReduxPinia 或自定义 Store),将状态抽离到独立的库中,主应用和子应用共同访问。

实现方式:
  1. 封装全局 Store:将状态管理逻辑封装为独立的 umd 模块(如 global-store.js),暴露 getStatedispatch 等方法。

    javascript

    运行

    // global-store.js(基于 Redux 简化)
    let state = { user: null, theme: 'light' };
    const listeners = [];
    
    export const globalStore = {
      getState: () => ({ ...state }), // 返回状态副本,避免直接修改
      dispatch: (action) => {
        switch (action.type) {
          case 'SET_USER':
            state.user = action.payload;
            break;
          case 'SET_THEME':
            state.theme = action.payload;
            break;
        }
        // 通知所有监听者
        listeners.forEach(listener => listener(state));
      },
      subscribe: (listener) => {
        listeners.push(listener);
        return () => {
          const index = listeners.indexOf(listener);
          listeners.splice(index, 1);
        }; // 返回解绑函数
      }
    };
    
  2. 主应用初始化 Store

    javascript

    运行

    // 主应用
    import { globalStore } from './global-store';
    window.globalStore = globalStore;
    // 初始化状态
    globalStore.dispatch({ type: 'SET_USER', payload: { id: 1, name: '张三' } });
    
  3. 子应用使用 Store

    javascript

    运行

    // Vue 子应用
    export default {
      data() {
        return { user: null };
      },
      mounted() {
        // 初始化状态
        this.user = window.globalStore.getState().user;
        // 订阅状态变化
        this.unsubscribe = window.globalStore.subscribe((state) => {
          this.user = state.user;
        });
      },
      beforeDestroy() {
        this.unsubscribe(); // 解绑订阅
      },
      methods: {
        updateUser() {
          // 修改全局状态
          window.globalStore.dispatch({ type: 'SET_USER', payload: { id: 1, name: '李四' } });
        }
      }
    };
    
优缺点:
  • 优点

    • 状态管理规范(有明确的修改方式 dispatch),避免混乱。
    • 支持状态变更监听,新挂载的子应用可获取最新状态。
    • 可集成中间件(如日志、持久化)。
  • 缺点

    • 需要额外引入状态管理库,增加复杂度。
    • 需保证子应用使用相同的状态库 API。
适用场景:

中大型微前端应用,需要共享复杂状态(如用户权限、多应用共享数据)。

四、基于本地存储(localStorage/sessionStorage)

利用浏览器的本地存储(localStorage 持久化,sessionStorage 会话级)共享状态,通过监听存储事件感知变化。

实现方式:
  1. 写入状态到本地存储

    javascript

    运行

    // 主应用或子应用
    function setGlobalUser(user) {
      localStorage.setItem('globalUser', JSON.stringify(user));
    }
    
  2. 读取状态并监听变化

    javascript

    运行

    // 其他子应用
    export default {
      data() {
        return { user: null };
      },
      mounted() {
        // 初始化读取
        this.user = JSON.parse(localStorage.getItem('globalUser'));
        // 监听存储变化
        window.addEventListener('storage', (e) => {
          if (e.key === 'globalUser') {
            this.user = JSON.parse(e.newValue);
          }
        });
      }
    };
    
优缺点:
  • 优点:持久化存储(localStorage),页面刷新后状态不丢失;跨框架兼容。

  • 缺点

    • 存储容量有限(通常 5MB),不适合大量数据。
    • 仅支持字符串类型,需手动序列化 / 反序列化(JSON.stringify/parse)。
    • storage 事件有延迟,且同一页面内修改不会触发(需额外处理)。
适用场景:

需要持久化的全局状态(如用户登录状态、主题设置)。

五、基于微前端框架的内置能力

主流微前端框架(如 qiankunsingle-spa)提供了内置的状态共享方案,简化跨应用通信。

以 qiankun 为例:

qiankun 提供了 initGlobalState 方法创建全局状态池,支持主应用与子应用、子应用之间的状态共享。

  1. 主应用初始化全局状态

    javascript

    运行

    // 主应用
    import { initGlobalState } from 'qiankun';
    
    // 初始化状态
    const initialState = { user: { id: 1, name: '张三' } };
    const globalState = initGlobalState(initialState);
    
    // 监听状态变化
    globalState.onGlobalStateChange((newState, prev) => {
      console.log('状态变化:', newState, prev);
    });
    
    // 修改状态
    globalState.setGlobalState({ user: { id: 1, name: '李四' } });
    
  2. 子应用接入全局状态

    javascript

    运行

    // Vue 子应用(在入口文件中)
    export async function mount(props) {
      // 接收全局状态
      const { onGlobalStateChange, setGlobalState } = props;
    
      // 监听状态变化
      onGlobalStateChange((newState) => {
        console.log('子应用收到状态:', newState);
        // 更新 Vuex/Pinia 状态
        store.dispatch('setUser', newState.user);
      }, true); // 第二个参数为 true 表示立即执行一次
    
      // 子应用修改全局状态
      setGlobalState({ theme: 'dark' });
    }
    
优缺点:
  • 优点

    • 由微前端框架原生支持,适配框架的生命周期(如子应用挂载 / 卸载时自动清理)。
    • 避免全局变量污染,状态隔离性更好。
  • 缺点

    • 依赖特定框架(如 qiankun),迁移成本高。
适用场景:

使用 qiankun 等成熟微前端框架的项目。

六、方案选择建议

  1. 简单场景:优先用 全局变量 + 自定义事件,实现快、成本低。
  2. 复杂状态:使用 跨框架状态管理库(如封装 Redux)或 微前端框架内置能力(如 qiankun),保证状态可控。
  3. 持久化需求:结合 localStorage 存储,配合事件监听同步状态。
  4. 跨技术栈:避免依赖特定框架的状态库(如 Vuex 仅支持 Vue),优先用原生 JS 实现的方案。

总结

微前端状态共享的核心是在独立部署的应用间建立可靠的通信渠道,关键考虑因素:跨框架兼容性状态可控性性能易用性。实际项目中,常结合多种方案(如全局 Store + 本地存储持久化)满足不同场景需求。

websocket和http的区别?有哪些特性?

这个问题抓得很关键,WebSocket 和 HTTP 虽同属应用层协议,但核心设计目标和通信模式完全不同。核心结论:HTTP 是单向、无状态的请求 - 响应协议,WebSocket 是双向、持久化的全双工通信协议,二者特性和适用场景差异显著。

一、核心区别(核心差异点)

  1. 通信模式
  • HTTP:请求 - 响应模式,客户端主动发起请求,服务器被动响应,响应后连接断开(短连接)。
  • WebSocket:握手后建立持久连接,客户端和服务器可双向主动发送数据(全双工),连接持续存在直到主动关闭。
  1. 连接状态
  • HTTP:无状态,每次请求需重新携带身份信息(如 Cookie、Token),服务器不保存连接上下文。
  • WebSocket:有状态,连接建立后保持会话,服务器可识别同一客户端的连续通信。
  1. 连接建立
  • HTTP:基于 TCP 三次握手,直接建立连接,使用 http:// 或 https:// 协议头。
  • WebSocket:先通过 HTTP 协议发起握手请求(携带 Upgrade: websocket 头),服务器响应后切换为 WebSocket 协议,使用 ws:// 或 wss:// 协议头。
  1. 数据传输效率
  • HTTP:每次请求 / 响应需携带完整头部(如 Cookie、请求头),冗余数据多,适合短周期数据交互。
  • WebSocket:握手后传输仅含少量帧头部,数据 payload 占比高,且无需重复建立连接,适合高频、实时数据传输。

二、各自核心特性

1. HTTP 特性
  • 单向通信:仅客户端可发起请求,服务器无法主动推送数据。
  • 无状态:服务器不记录请求间的关联,需通过 Cookie、Session 等机制维持状态。
  • 短连接:默认响应后关闭连接,可通过 Keep-Alive 实现长连接(本质是复用连接,仍为请求 - 响应模式)。
  • 兼容性强:所有浏览器、服务器原生支持,应用场景广泛。
  • 支持缓存:可通过 Cache-ControlETag 等头信息实现数据缓存,减少重复请求。
2. WebSocket 特性
  • 全双工通信:客户端和服务器可同时发送数据,实时性强。
  • 持久连接:连接建立后持续存在,无需频繁握手,降低延迟。
  • 二进制 + 文本支持:可传输文本(UTF-8)或二进制数据(如文件、视频流),帧结构灵活。
  • 心跳机制:通过 ping/pong 帧维持连接,检测对方在线状态,避免连接被防火墙断开。
  • 跨域支持:握手阶段可通过 CORS 机制实现跨域通信,无需额外配置。

三、适用场景对比

  • HTTP 适用场景:普通网页请求、接口调用、文件下载、表单提交等非实时场景(如电商商品查询、登录验证)。
  • WebSocket 适用场景:实时聊天、实时数据推送(如股票行情、监控数据)、多人协作工具(如在线文档)、游戏实时交互等需要低延迟双向通信的场景。

总结

HTTP 是 “请求 - 响应” 的短连接协议,侧重简单、通用的一次性数据交互;WebSocket 是 “双向持久” 的全双工协议,侧重实时、高频的双向数据传输。二者并非替代关系,而是互补 —— 很多项目中会同时使用(如通过 HTTP 完成登录,再通过 WebSocket 接收实时通知)。

对比维度HTTPWebSocket
核心通信模式客户端主动请求 → 服务器被动响应(单向请求 - 响应)握手后双向主动发送数据(全双工通信)
连接状态无状态,不保存连接上下文有状态,维持会话关联
连接类型短连接(默认响应后关闭),可通过 Keep-Alive 复用连接持久连接(建立后持续存在,直到主动关闭)
连接建立方式基于 TCP 三次握手,直接建立连接先通过 HTTP 握手(携带 Upgrade: websocket 头),再切换协议
协议标识http://(明文)、https://(加密)ws://(明文)、wss://(加密)
数据传输效率每次交互携带完整请求 / 响应头,冗余数据多仅帧头部少量开销,payload 占比高,无重复握手损耗
服务器主动推送不支持,需通过轮询、长轮询模拟原生支持,服务器可主动向客户端推送数据
数据传输格式文本(如 JSON、HTML)、二进制(文件等)文本(UTF-8)、二进制数据(帧结构支持)
状态维持方式依赖 Cookie、Session、Token 等外部机制连接本身维持状态,无需额外携带身份信息
缓存支持原生支持(Cache-Control、ETag 等)不支持缓存
跨域支持依赖 CORS 机制握手阶段通过 CORS 实现,原生支持跨域
心跳机制无原生支持,需手动实现原生支持 ping/pong 帧,维持连接活性
适用场景普通接口调用、网页请求、文件下载、表单提交等非实时场景实时聊天、股票行情、监控数据推送、多人协作、游戏交互等实时场景
兼容性所有浏览器、服务器原生支持,兼容性极强现代浏览器(IE10+)、主流服务器均支持

vue如何实现动态路由

Vue 中的动态路由指的是根据后端数据(如用户权限、后端返回的路由配置)动态生成路由规则,实现不同用户看到不同页面的需求(如管理员和普通用户的路由权限不同)。动态路由的核心是通过 Vue Router 的 API 动态添加路由规则,而非在初始化时写死。

一、动态路由的实现步骤

1. 初始化基础路由

首先定义无需权限的基础路由(如登录页、404 页),作为路由的初始配置。

javascript

运行

// router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Login from '../views/Login.vue';
import NotFound from '../views/NotFound.vue';

Vue.use(VueRouter);

// 基础路由(无需权限)
const baseRoutes = [
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '*',
    name: 'NotFound',
    component: NotFound
  }
];

const router = new VueRouter({
  routes: baseRoutes
});

export default router;
2. 从后端获取动态路由配置

用户登录后,通过接口获取该用户对应的路由权限配置(通常是一个路由规则数组)。后端返回的格式一般包含 pathnamecomponentchildren 等字段,例如:

json

// 后端返回的动态路由示例
[
  {
    "path": "/dashboard",
    "name": "Dashboard",
    "meta": { "title": "仪表盘", "requiresAuth": true },
    "component": "Dashboard", // 组件名(前端需映射到实际组件)
    "children": [
      {
        "path": "analysis",
        "name": "Analysis",
        "component": "Analysis",
        "meta": { "title": "数据分析" }
      }
    ]
  },
  {
    "path": "/user",
    "name": "User",
    "meta": { "title": "用户管理", "requiresAuth": true },
    "component": "User"
  }
]
3. 动态路由映射(组件路径转换)

后端返回的 component 通常是字符串(如 "Dashboard"),需要转换为前端实际的组件路径(如 () => import('../views/Dashboard.vue'))。因此,需创建一个 “组件映射表”:

javascript

运行

// router/componentMap.js
// 映射组件名到实际组件
export const componentMap = {
  Dashboard: () => import('../views/Dashboard.vue'),
  Analysis: () => import('../views/Analysis.vue'),
  User: () => import('../views/User.vue')
  // 其他组件...
};

然后编写一个递归函数,将后端返回的路由配置转换为 Vue Router 可识别的格式:

javascript

运行

// router/utils.js
import { componentMap } from './componentMap';

// 递归转换路由配置
export function convertRoutes(routes) {
  return routes.map(route => {
    const newRoute = {
      path: route.path,
      name: route.name,
      meta: route.meta || {},
      // 转换 component 为实际组件
      component: componentMap[route.component]
    };
    // 处理子路由(递归)
    if (route.children && route.children.length > 0) {
      newRoute.children = convertRoutes(route.children);
    }
    return newRoute;
  });
}
4. 动态添加路由

在用户登录成功后,获取后端路由配置,转换后通过 Vue Router 的 addRoutes(Vue Router 3)或 addRoute(Vue Router 4)方法动态添加路由。

Vue Router 3 示例:

javascript

运行

// store/index.js(Vuex 中处理登录逻辑)
import router from '../router';
import { convertRoutes } from '../router/utils';
import api from '../api';

const store = new Vuex.Store({
  state: {
    userRoutes: [] // 存储动态路由
  },
  mutations: {
    setUserRoutes(state, routes) {
      state.userRoutes = routes;
    }
  },
  actions: {
    async login({ commit }, { username, password }) {
      // 1. 登录获取 token
      const { token } = await api.login(username, password);
      localStorage.setItem('token', token);

      // 2. 获取用户路由配置
      const userRouteConfig = await api.getUserRoutes();

      // 3. 转换路由配置
      const dynamicRoutes = convertRoutes(userRouteConfig);

      // 4. 动态添加路由(通常添加到一个公共的父路由下,如 /layout)
      // 假设存在一个布局路由,动态路由作为其子路由
      dynamicRoutes.forEach(route => {
        router.addRoute('Layout', route); // 'Layout' 是父路由的 name
      });

      // 5. 存储路由到 Vuex,用于渲染侧边栏
      commit('setUserRoutes', dynamicRoutes);

      // 6. 跳转到首页
      router.push('/dashboard');
    }
  }
});

注意:Vue Router 3 中 addRoutes 可直接添加数组,但更推荐用 addRoute 逐个添加(支持嵌套路由)。

Vue Router 4 示例(Vue 3):

Vue Router 4 中 addRoutes 已废弃,需用 addRoute 逐个添加,且支持链式调用:

javascript

运行

// 动态添加路由(Vue Router 4)
const dynamicRoutes = convertRoutes(userRouteConfig);
dynamicRoutes.forEach(route => {
  router.addRoute(route); // 直接添加顶级路由
  // 或添加到父路由:router.addRoute('Layout', route)
});
5. 处理路由刷新丢失问题

动态添加的路由在页面刷新后会丢失(因为 addRoute 是运行时动态添加的,刷新后初始化代码会重新执行,只加载基础路由)。解决方案:

  • 在 router.beforeEach 导航守卫中,判断用户是否已登录且动态路由未加载,若未加载则重新获取并添加:

javascript

运行

// router/index.js
import store from '../store';
import { convertRoutes } from './utils';
import api from '../api';

router.beforeEach(async (to, from, next) => {
  const token = localStorage.getItem('token');
  const isLogin = !!token;

  // 未登录且访问非登录页,跳转到登录页
  if (!isLogin && to.name !== 'Login') {
    return next('/login');
  }

  // 已登录且动态路由未加载,重新添加路由
  if (isLogin && store.state.userRoutes.length === 0) {
    try {
      const userRouteConfig = await api.getUserRoutes();
      const dynamicRoutes = convertRoutes(userRouteConfig);
      // 动态添加路由
      dynamicRoutes.forEach(route => {
        router.addRoute('Layout', route);
      });
      // 存储到 Vuex
      store.commit('setUserRoutes', dynamicRoutes);
      // 重新导航到目标路由(避免因路由未加载导致的 404)
      return next({ ...to, replace: true });
    } catch (error) {
      // 获取路由失败,可能是 token 过期,退出登录
      localStorage.removeItem('token');
      return next('/login');
    }
  }

  next();
});

二、动态路由的核心原理

动态路由的本质是通过 Vue Router 提供的 API 动态修改路由表,核心依赖:

  • addRoute(Vue Router 3/4):向路由表中添加新的路由规则(支持嵌套路由,需指定父路由 name)。
  • 导航守卫(beforeEach):在路由跳转前检查动态路由是否已加载,确保刷新后重新添加。
  • 组件映射:将后端返回的组件名转换为前端实际的组件路径(通过 import 动态导入)。

三、注意事项

  1. 404 路由的位置:404 路由(path: '*')必须放在最后加载,否则会拦截动态添加的路由。动态路由添加后,404 路由会正确匹配未定义的路径。

  2. 路由权限的细粒度控制:除了动态添加路由,还需在导航守卫中检查用户是否有权限访问某个路由(结合 meta 中的权限标识):

    javascript

    运行

    router.beforeEach((to, from, next) => {
      if (to.meta.requiresAuth && !hasPermission(to.name)) {
        return next('/403'); // 无权限跳转到 403 页
      }
      next();
    });
    
  3. Vue Router 版本差异

    • Vue Router 3 用 addRoutes(routes) 批量添加,addRoute(route) 单个添加。
    • Vue Router 4 仅支持 addRoute(route) 或 addRoute(parentName, route),且添加后需通过 next({ ...to, replace: true }) 重新导航。
  4. 侧边栏渲染:动态路由通常需要在侧边栏展示,可直接使用 Vuex 中存储的 userRoutes 遍历渲染(配合 v-for 和路由元信息 meta.title)。

总结

Vue 动态路由的实现流程可概括为:初始化基础路由 → 登录后获取后端路由配置 → 转换为前端可识别的路由格式 → 动态添加到路由表 → 处理刷新丢失问题

核心依赖 Vue Router 的 addRoute 方法和导航守卫,通过后端返回的权限配置动态生成路由,实现不同用户的路由权限隔离。

vue的路由和页面内容是如何切换的

Vue 中路由与页面内容的切换,本质是通过 Vue Router 管理 URL 与组件的映射关系,并在 URL 变化时动态渲染对应的组件。整个过程由路由实例、路由匹配、组件渲染三个核心部分协同完成,具体流程如下:

一、核心原理:URL 与组件的映射

Vue Router 的核心是建立 URL 路径与组件的对应关系(路由规则),当 URL 变化时,Vue Router 会找到匹配的路由规则,渲染对应的组件到页面指定位置(通常是 <router-view> 标签)。

1. 路由规则的定义

首先在路由配置中定义 path(URL 路径)与 component(组件)的映射,例如:

javascript

运行

// router/index.js
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About }
];

const router = new VueRouter({ routes });
2. <router-view>:内容渲染的占位符

在根组件(如 App.vue)中使用 <router-view> 标签,它是路由匹配到的组件的渲染出口。当 URL 变化时,Vue Router 会将匹配到的组件渲染到 <router-view> 位置。

vue

<!-- App.vue -->
<template>
  <div>
    <!-- 导航链接 -->
    <router-link to="/home">首页</router-link>
    <router-link to="/about">关于</router-link>
    
    <!-- 组件渲染出口:URL 匹配的组件会在这里显示 -->
    <router-view></router-view>
  </div>
</template>

二、路由切换的完整流程

当用户点击 <router-link> 或调用 this.$router.push() 时,路由切换的流程如下:

1. 触发 URL 变化
  • 通过 <router-link> :点击 <router-link to="/about"> 时,会自动调用 $router.push('/about'),修改 URL(通过 HTML5 History API 或 hash 模式)。

  • 通过编程式导航:调用 this.$router.push('/about') 直接修改 URL。

    注意:Vue Router 默认使用 HTML5 History 模式(history.pushState)修改 URL,不会触发页面刷新;若浏览器不支持,则降级为 hash 模式(#/about)。

2. 路由匹配:找到对应的组件

URL 变化后,Vue Router 会遍历路由规则(routes 数组),通过路径匹配算法找到与当前 URL 匹配的路由对象(route)。

  • 匹配规则:优先精确匹配,再尝试模糊匹配(如动态路由 :id、通配符 *)。
  • 例如:URL 为 /about 时,匹配 { path: '/about', component: About }
3. 组件卸载与挂载

找到匹配的路由后,Vue Router 会:

  • 卸载旧组件:触发旧组件的 beforeRouteLeave 导航守卫 → 执行 beforeDestroydestroyed 生命周期(若未被 <keep-alive> 缓存)。

  • 挂载新组件:触发新组件的 beforeRouteEnter 导航守卫 → 执行 beforeCreatecreatedmounted 生命周期 → 将新组件渲染到 <router-view> 位置。

    若组件被 <keep-alive> 缓存,则触发 deactivated(旧组件失活)和 activated(新组件激活),而非销毁 / 创建。

4. 导航守卫的作用(可选拦截)

在路由切换过程中,导航守卫(如 beforeEachbeforeEnter)可以拦截切换过程,实现权限校验、登录判断等逻辑:

javascript

运行

// 全局前置守卫:判断是否登录
router.beforeEach((to, from, next) => {
  if (to.path !== '/login' && !isLogin()) {
    next('/login'); // 未登录,强制跳转到登录页
  } else {
    next(); // 允许路由切换
  }
});

三、嵌套路由的切换逻辑

当路由存在嵌套关系(如父路由包含子路由)时,切换逻辑会递归执行:

  1. 定义嵌套路由

javascript

运行

const routes = [
  {
    path: '/user',
    component: UserLayout, // 父组件
    children: [
      { path: 'profile', component: UserProfile }, // 子路由1
      { path: 'settings', component: UserSettings } // 子路由2
    ]
  }
];
  1. 嵌套 <router-view> :父组件(UserLayout)中需包含 <router-view>,用于渲染子路由对应的组件:

vue

<!-- UserLayout.vue -->
<template>
  <div class="user-layout">
    <h2>用户中心</h2>
    <router-link to="/user/profile">个人资料</router-link>
    <router-link to="/user/settings">设置</router-link>
    
    <!-- 子组件渲染出口 -->
    <router-view></router-view>
  </div>
</template>
  1. 切换过程:当 URL 从 /user/profile 切换到 /user/settings 时:

    • 父组件 UserLayout 保持挂载状态(不销毁)。
    • 子路由匹配到 UserSettings,卸载 UserProfile 并挂载 UserSettings,渲染到父组件的 <router-view> 中。

四、URL 变化不刷新页面的原因

Vue Router 切换路由时不会导致页面刷新,核心原因是:

  • 使用 HTML5 History APIhistory.pushState)或 hash 模式location.hash)修改 URL,这两种方式都只会改变 URL 而不触发浏览器的默认页面刷新行为。
  • 路由切换本质是单页应用(SPA)的内部视图更新,通过替换 <router-view> 中的组件实现内容变化,整个过程在同一个 HTML 页面中完成。

总结

Vue 路由与页面内容的切换流程可概括为:URL 变化 → 路由匹配找到对应组件 → 卸载旧组件 / 挂载新组件 → 在 <router-view> 中渲染新组件

核心依赖:

  • 路由规则定义 path 与 component 的映射。
  • <router-view> 作为组件渲染的出口。
  • Vue Router 对 URL 变化的监听和组件切换的管理。

这种机制实现了单页应用的无刷新页面切换,提升了用户体验和应用性能。