【Vue3】day78-vue-router-basic

作者:mario 发布时间: 2026-09-03 阅读量:4 评论数:0

Day 78 · Vue Router 基础 — 三屏路由化:URL 成为状态

今天让 Day 64 手写的 ScreenRouter 开始退役:三屏切换交给 Vue Router,URL 成为屏幕状态的一部分——/a /b /c 分别对应三屏,刷新不丢、后退可用、可收藏分享。这是替换周的第一刀:只动路由,数据层(useScreenStore)原样服役,替换后跑联调清单验证"行为零变化"(URL 行为除外——这是本周唯一允许的改进)。


目录


一、安装与路由表

npm install vue-router@4
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";

/**
 * 路由表:三屏 = 三条路由
 * 设计决策:
 * 1. path 短名(/a /b /c)——大屏是演示场景,URL 简短即专业
 * 2. history 模式(createWebHistory)——URL 干净;需要服务器配置回退(见坑 4)
 * 3. name 字段必写——轮播调度器按 name 切换,与 path 解耦(改路径不动逻辑)
 */
export const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: "/", redirect: "/a" },                        // 根路径 → 屏 A
    { path: "/a", name: "overview",  component: () => import("../screens/ScreenA.vue") },
    { path: "/b", name: "monitor",   component: () => import("../screens/ScreenB.vue") },
    { path: "/c", name: "topology",  component: () => import("../screens/ScreenC.vue") },
    { path: "/:pathMatch(.*)*", redirect: "/a" },         // 兜底(第五节)
  ],
});
// src/main.ts —— 注册路由(先主题 → 再路由 → 后挂载)
import { createApp } from "vue";
import { createPinia } from "pinia";           // Day 80 再启,今天先注释
import App from "./App.vue";
import { router } from "./router";
import "./styles/tokens.css";

createApp(App).use(router).mount("#app");

注意路由的懒加载写法(() => import(...)):三屏各自分包,首屏只加载屏 A 的 chunk——原版三屏全量加载的改进,记进对照表。


二、router-view:单挂载点的世界

2.1 App.vue 的改造

<!-- App.vue:从"三屏并存 v-show"变为"单挂载点" -->
<script setup lang="ts">
// scale 壳逻辑原样保留(Day 71 的 onMounted/onUnmounted 成对纪律不变)
</script>

<template>
  <div class="screen-frame">
    <div class="screen-canvas" :style="{ transform: `scale(${scale})` }">
      <header class="top-bar">
        <h1>智慧工厂监控中心</h1>
        <ConnIndicator :state="connState" />
      </header>

      <!-- 路由出口:当前路由的屏幕组件在这里渲染(同时只有一个) -->
      <router-view />

      <!-- 底部指示器(第三节) -->
      <ScreenNav />
    </div>
  </div>
</template>

2.2 单挂载点带来的行为变化(今天必须理解的事)

v-show 时代(Week 11):三屏始终挂载,ECharts 实例常驻,切屏零成本。

router-view 时代:切屏 = 卸载旧屏 + 挂载新屏

切到 /b:
  ScreenA unmount → VChart dispose → ECharts 实例销毁
  ScreenB mount   → VChart init    → ECharts 实例重建(约 100-200ms)

这正是 Day 76 坑 1 的场景放大版。本周的解法是 Day 82 的 KeepAlive(缓存组件实例,切走不销毁)。今天先接受"切换略重",把路由链路跑通;明天过渡 + 后天缓存,三步走完整个替换。

🎯 心智对照:原生版 Day 64 选"opacity 互斥保实例",本质就是手动实现的 KeepAlive。今天体验"没有缓存的痛",后天才会真正理解 KeepAlive 解决什么


<!-- src/components/ScreenNav.vue -->
<script setup lang="ts">
/**
 * 底部屏幕指示器:圆点 + 手动切换
 * 迁移自原生版 Day 64 的圆点指示器
 */
import { computed } from "vue";
import { useRoute } from "vue-router";

const route = useRoute();

// 当前屏的 name(路由表里定义的),用于高亮
const current = computed(() => route.name);

// 三屏配置:单一来源,轮播调度器(Day 79)也读它
export const SCREENS = [
  { name: "overview",  label: "总览", dwell: 15 },
  { name: "monitor",   label: "监控", dwell: 20 },
  { name: "topology",  label: "拓扑", dwell: 15 },
] as const;
</script>

<template>
  <nav class="screen-nav">
    <!-- router-link:声明式导航,激活态自动加 class -->
    <router-link
      v-for="s in SCREENS"
      :key="s.name"
      :to="{ name: s.name }"
      class="screen-nav__dot"
      :class="{ 'is-active': current === s.name }"
      :title="s.label"
    />
  </nav>
</template>

<style scoped>
.screen-nav {
  position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 12px;
}
.screen-nav__dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--border-card); cursor: pointer; transition: all 0.2s;
}
.screen-nav__dot.is-active {
  background: var(--raw-blue);
  box-shadow: 0 0 8px var(--border-glow);      /* token 同源 */
}
</style>

router-link vs 手写点击:原生版要 addEventListener + 调 router.switchTo;router-link 声明式完成,且自动处理激活态、键盘可达性。


四、与 v-show 时代的本质差异

维度 v-show 三屏(Week 11) router-view(今天)
URL 不变 /a /b /c 即状态
刷新 回到首屏 停留当前屏 ✅
后退键 无效 逐屏回退 ✅
分享 不可 发链接直达某屏 ✅
屏幕实例 常驻(内存 × 3) 切走即销毁(Day 82 缓存找回)
代码分割 全量加载 懒加载按屏分包 ✅

工业场景的现实意义:值班室的大屏重启后自动回到"拓扑页"而不是总览页;运维排查时可以直接把 /b 发给同事——URL 成为工作流的一部分。


五、404 与重定向

// 路由表的两条兜底规则(第一节已含,这里解释为什么)
{ path: "/", redirect: "/a" },                 // 根路径:用户直接开 IP,落到默认屏
{ path: "/:pathMatch(.*)*", redirect: "/a" },  // 任意未知路径:兜底回屏 A

大屏是封闭演示场景,404 页没有意义——兜底重定向比错误页体验更好。若未来做多用户系统再改 404,记待办。


六、常见坑点

坑 1:v-show 残留导致"两套切换逻辑打架"

ScreenA/B/C 组件内部还有 Week 11 的 v-show 相关 class(screen-hidden 等)。路由接管后这些永远不生效,但会误导调试。清理:全局 CSS 里的 .screen-hidden 与相关过渡一并删除。

坑 2:路由切换后图表 0 尺寸 init

router-view 挂载新屏时,VChart 的双保险(Day 76 的 tryInit + ResizeObserver)自动兜住——昨天封装的基座今天直接兑现价值。若仍失败,检查屏组件根元素的 grid 区域是否声明了高度(grid 布局的 1fr 依赖父级高度)。

坑 3:devtools 里路由不显示

createWebHistory 需要 Vue Devtools 6+。另外确认 app.use(router)mount 之前——顺序反了路由系统未初始化。

坑 4:刷新 404(部署时)

history 模式下刷新 /b,静态服务器找不到对应文件。开发环境 Vite 自动处理;部署时需要回退配置(把所有路径指回 index.html)。今天只记录,Week 14 收官时处理部署。


七、自测挑战

T1 · 路由替换(70 分钟)

完成第一至三节:三屏路由化、指示器切换、懒加载分包(Network 面板验证三屏是三个 chunk)、刷新停留当前屏。手写 ScreenRouter 此刻还有轮播职责,暂不删除(Day 79 完成联动后退役)。

T2 · 行为对齐回归(30 分钟)

跑 Day 69 联调清单的路由部分:快速连切无白屏、数据流五断点正常(数据层没动,应该全绿)。发现数据异常先查是否有人绕过 useScreenStore 私连。

T3 · URL 工作流体验(20 分钟)

录一段 30 秒操作:切到 /b → 刷新(停留)→ 后退(回 /a)→ 直接改地址栏 /c。把"URL 即状态"的体验写进对照表。


八、总结

环节 要点
路由表 短路径 + name 解耦 + 懒加载分包
router-view 单挂载点:切屏 = 卸载 + 挂载(Day 82 缓存解决)
router-link 声明式导航 + 自动激活态
URL 即状态 刷新停留、后退可用、可分享——工作流改进
兜底 重定向优先于 404(大屏场景)

路由骨架就位。明天补上轮播调度器与守卫,让手写 ScreenRouter 彻底退役。

评论