vue-split-screen:让 Vue Router 的导航轨迹变成两个页面

首页 编程分享 JQUERY丨JS丨VUE 正文

huali 转载 编程分享 2026-09-14 21:12:51

简介 vue-split-screen 以单一 Vue Router 支撑双栏界面:导航形成可序列化轨迹,双栏显示轨迹末尾两页;轨迹写入浏览器历史,前进返回可恢复,并提供面板路由与页面缓存。


在手机上,从列表打开详情,当前页面被替换是很自然的交互。到了宽屏或折叠屏上,用户往往希望列表留在左边,详情出现在右边,同时还能继续用浏览器的返回和前进。

vue-split-screen 做的事情很集中:它保留一套 Vue Router,把导航过程中的页面记录成一条轨迹,再把轨迹末尾的两个页面呈现在两个面板里。列表 / 详情、父子页面和折叠屏展开后的双栏视图,都可以沿用同一套路由配置。

先把双栏问题还原成一条导航轨迹

直接放两套 RouterView,很快就会遇到两个地址、两份当前路由和两套浏览器历史的问题。这个项目没有再维护一个平行路由,而是把每次成功导航后的页面关系保存下来:

trail: A -> B -> C
view:       B | C

每个节点只有两个字段:

interface SplitRouteNode {
  id: string;
  fullPath: string;
}

fullPath 表示页面实际地址,包含查询参数和 hash。id 表示这一次页面实例在轨迹中的身份,所以两次访问相同地址,也可以对应两个不同节点。这样处理后,路由地址和页面实例就不会混在一起。

双栏只取轨迹末尾的两个节点。假设当前显示的是 B | C,从不同页面发起导航,结果并不一样:

操作 新轨迹
C.push(D) [A, B, C, D]
B.push(D) [A, B, D]
C.replace(D) [A, B, D]
B.replace(D) [A, D]

push 会保留发起导航的节点,replace 会把它也替换掉。这个规则由纯 TypeScript 函数 navigateTrail 完成,和 Vue 组件、浏览器 API 都没有关系,因此可以单独测试。

从哪个面板点击,就从哪个节点继续导航

双栏场景需要知道按钮属于哪个页面,否则在左侧面板点击跳转时,很容易把右侧页面也当成导航起点。SplitScreen 会在页面上下文里为 useRouter() 注入面板局部的 pushreplace,项目另外提供了 useSplitRouter(),让这层依赖更明确。

页面组件里可以使用项目提供的 useSplitRouter()

<script setup lang="ts">
import { useSplitRouter } from 'vue-split-screen';

const router = useSplitRouter();
</script>

<template>
  <button @click="router.push('/detail/42')">
    Open detail
  </button>
</template>

SplitScreen 为每个页面代理注入了自己的路由上下文。这个上下文里,pushreplace 会把当前节点的 id 一起传给历史控制器,控制器再根据节点位置计算下一条轨迹。对象形式的导航也会以发起导航的页面作为解析基准,所以从左侧页面只修改查询参数时,目标仍然落在正确的节点上。

同一个上下文还提供了 useSplitRouteNode(),可以拿到当前节点的 idfullPath。项目推荐在页面逻辑里使用这些组合式函数,把“页面属于哪一个面板”这件事写得明确。

把轨迹写进每一个浏览器历史条目

导航轨迹如果只放在组件内存里,浏览器返回后就无法知道之前显示过哪些页面。vue-split-screen 把它序列化到 history.state

{
  __vueSplitScreen: {
    version: 1,
    trail: [
      { id: 'A', fullPath: '/list' },
      { id: 'B', fullPath: '/detail/42' },
    ],
  },
}

Vue Router 仍然负责地址栏、导航守卫以及浏览器的 pushStatereplaceState。项目只在调用路由方法前准备好下一条轨迹,并在 afterEach 中确认导航结果。

这里有几个容易被忽略的时机:

  • 重定向成功后,最后一个节点会更新为重定向后的最终地址。
  • 守卫中止、导航取消或重复导航失败时,轨迹不会被提前修改。
  • 浏览器执行 Back 或 Forward 时,控制器直接读取目标历史条目里的轨迹。
  • 没有项目状态的初始历史条目会补上一条包含当前路由的初始轨迹,格式不正确的外部状态则会被忽略。

因此,Back 和 Forward 恢复的是当时的页面关系,而不只是某个 URL。对于需要保留用户浏览上下文的列表 / 详情页面,这个差别很重要。

用一个 RouterView 渲染当前页和伴随页

接入时,SplitScreen 放在 RouterView 的插槽里,当前页面继续使用 Vue Router 提供的 Component

<script setup lang="ts">
import { ref } from 'vue';
import { SplitScreen } from 'vue-split-screen';

const split = ref(true);
const reverse = ref(false);
const maxInactivePages = ref(0);
</script>

<template>
  <RouterView v-slot="{ Component }">
    <SplitScreen
      :turn-on="split"
      :split-reverse="reverse"
      :max-inactive-pages="maxInactivePages"
    >
      <component :is="Component" />
      <template #placeholder>
        Select a page to open the second pane.
      </template>
    </SplitScreen>
  </RouterView>
</template>

当前节点直接渲染插槽里的组件,伴随节点则通过已解析的路由记录渲染对应组件。每个页面外面都有一个 ScreenProxy,它负责提供面板自己的 routerouter 和节点信息。

这里有一个实际的接入边界:默认插槽需要直接渲染 RouterView 提供的 Component。任意额外的包装节点和兄弟节点不会被项目重新构造,独立嵌套的 RouterView 也不属于稳定契约。全局组件属性上的 $router$route 仍然是应用级对象,页面逻辑应使用组合式函数获取面板上下文。

页面离开视野后,要不要保留状态

双栏只展示最后两个页面,前面的页面会进入非活动状态。默认的 maxInactivePages0,页面一旦离开可见的两个位置就会卸载。需要保留输入框草稿、滚动位置或局部组件状态时,可以设置一个正整数:

<SplitScreen :max-inactive-pages="2">
  ...
</SplitScreen>

这个数字只计算非活动页面,当前两个面板不占用名额。保留策略按最近使用顺序淘汰,重新回到缓存中的页面时会触发 Vue 的 activated,离开可见区域时会触发 deactivated。项目的演示页把这些生命周期计数直接显示出来,便于观察卸载、缓存和重新激活的区别。

Playground:用可操作的行为说明验证路由

仓库里的 playground 提供了 A、B、C、D 四个页面,可以从任意面板执行 pushreplace、重定向和被守卫拦截的导航。顶部还可以切换双栏方向、单页模式、保留数量,以及浏览器的 Back 和 Forward。

运行演示:

pnpm play

实现里的模型测试覆盖轨迹转移、页面呈现和 LRU 保留选择,浏览器测试则直接验证真实的 Vue 组件行为,包括以下场景:

  • 从伴随页面发起 push,轨迹按发起节点分支。
  • 返回和前进恢复精确的历史轨迹。
  • 重定向只提交最终地址,失败导航不改变页面。
  • 页面默认及时卸载,开启保留后按 LRU 淘汰并重新激活缓存页面。
  • 页面守卫和 beforeRouteEnter 仍然拿到正确的组件实例。

项目根目录的完整检查命令是:

pnpm check
pnpm test:browser

其中 pnpm check 会依次执行 lint、类型检查、单元测试、库构建和 playground 构建,浏览器行为测试单独通过 Vitest Browser Mode 在 Chromium 中运行。

适合什么场景

如果应用已经使用 Vue Router,又希望在宽屏上同时保留列表和详情,或者让折叠屏展开后展示两个有关联的页面,这个项目提供了一种比较窄的接入方式:路由仍只有一套,页面关系变成可序列化的轨迹,布局层只负责呈现轨迹末尾的节点。

它不试图把 Vue Router 变成两个互相独立的路由器。需要多个地址栏、多个完全独立的导航历史,或者复杂的嵌套路由树时,仍然应该按应用自身的路由模型设计。

项目地址:github.com/zcf0508/vue…,安装命令如下:

pnpm add vue-split-screen

转载链接:https://juejin.cn/post/7683846471340032051


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云