Skip to content

Vue 状态、路由与宿主协同 ​

一句话定位 ​

这一页不是把 ref / reactive / Pinia / vue-router 当成零散 API 罗列,而是把它们放回一个统一判断:H5 页面里的局部响应式状态、跨页面共享状态、路由状态、宿主协同状态,到底分别该挂在哪一层。

为什么需要 ​

如果只会背 API,很容易在真实项目里出现这些问题:

  • 一个筛选条件既存在组件本地,又存在 Pinia,又写进 URL,最后三份状态互相覆盖
  • vue-router 跳页后状态恢复逻辑混乱,不知道该信页面缓存、Pinia 还是 query
  • WebView 宿主传入登录态、token、灰度参数后,H5 内部又自己维护一套,导致双写漂移
  • 页面回退后以为是“状态丢了”,其实只是组件被卸载了,或者缓存策略没配对

所以这一页的重点不是“Vue 怎么写响应式”,而是:

  • 局部状态 放组件
  • 业务共享状态 放 Pinia
  • 可链接、可恢复、可分享的状态 放路由参数
  • 宿主注入状态 放桥接边界,不要和页面内部状态混写

先建立哪一层心智 ​

建议先把 Vue / H5 里的状态拆成四层:

  1. 组件局部状态:输入框内容、loading、展开收起、tab 临时选中态
  2. 页面级共享状态:列表筛选、会话上下文、用户信息、实验开关
  3. 路由状态:页面身份、query、返回路径、恢复入口
  4. 宿主协同状态:登录态、设备信息、支付结果、Bridge 下发参数

只要先分清这四层,后面看 ref / reactive / Pinia / vue-router / keep-alive / WebView 都不会混。

代码索引 ​

主题本页定位
ref / reactive组件局部响应式建模
Pinia跨页面共享状态与业务状态收口
vue-router路由驱动状态与恢复路径
keep-alive页面缓存,不等于状态总线
宿主协同App 注入参数、登录态、桥接回流

底层机制 ​

1. ref / reactive:组件局部状态优先 ​

ref 和 reactive 首先解决的是当前组件或当前组合函数内部的响应式建模,不是天然的跨页面状态方案。

ts
const keyword = ref('')
const filters = reactive({
  category: 'all',
  sort: 'latest',
})
  • 可能执行顺序:用户输入关键字 → keyword 更新 → 依赖它的视图重新渲染;用户切换筛选项 → filters 更新 → 列表请求参数重新计算。
  • 预期现象:局部交互改动会立刻反馈到当前页面,不需要全局状态介入。
  • 观察重点:局部临时状态优先留在组件层,不要一开始就提升到 Pinia。

适合:

  • 表单中间态
  • 当前页面的 loading / error / panel 展开状态
  • 派生 UI 状态

不适合:

  • 多页面共享业务状态
  • 需要被 URL 恢复或分享的状态
  • 宿主注入、登录态、会话级参数

2. Pinia:收口跨页面共享状态 ​

Pinia 更适合承接业务共享状态,而不是替代所有组件状态。

ts
export const useSearchStore = defineStore('search', {
  state: () => ({
    keyword: '',
    category: 'all',
    resultIds: [] as string[],
  }),
})
  • 可能执行顺序:页面 A 更新 store → 跳到页面 B → 页面 B 直接读取同一份共享状态。
  • 预期现象:跨页面共享的数据不需要靠 props 层层传递。
  • 观察重点:store 存的是“业务共享状态”,不是所有临时输入过程都往里塞。

适合:

  • 用户会话状态
  • 搜索条件在多个页面复用
  • 列表状态与详情页协同
  • 宿主下发的全局配置快照

常见误区:

  • 把输入框每个击键过程都写进 Pinia,导致全局订阅噪音过大
  • 把可恢复到 URL 的状态只存在 store,不写入路由参数,刷新即丢

3. vue-router:让状态具备可恢复性 ​

有一类状态天然属于路由:

  • 当前页面是谁
  • 查询参数是什么
  • 返回路径怎么走
  • 这个页面能不能被刷新后恢复
ts
router.push({
  name: 'search',
  query: { keyword: keyword.value, category: filters.category },
})
  • 可能执行顺序:用户修改筛选条件 → 同步更新 query → 页面刷新或分享链接后,仍可从 query 恢复入口状态。
  • 预期现象:同一页面的身份与恢复路径由 URL 定义,而不是只靠内存 store。
  • 观察重点:能被复制、刷新、分享、恢复的状态,应优先进入路由层。

适合进入路由的状态:

  • 搜索词
  • tab 标识
  • 详情页 id
  • 页面模式(如 mode=trial)

不适合进入路由的状态:

  • 大对象业务缓存
  • 高频变化的瞬时输入过程
  • 敏感宿主注入信息

4. keep-alive:页面缓存,不是状态分层替代品 ​

很多人看到“页面返回后状态还在”,就误以为状态管理问题解决了。其实 keep-alive 回答的是:

  • 组件实例是否复用

它不回答:

  • 共享业务状态应该放哪
  • 刷新后还能不能恢复
  • 宿主参数回流后怎么收口
vue
<keep-alive>
  <router-view v-if="route.meta.keepAlive" />
</keep-alive>
  • 可能执行顺序:页面 A 首次进入创建实例 → 切到页面 B → 返回页面 A 时实例被复用。
  • 预期现象:局部输入、滚动位置、部分组件状态可保留。
  • 观察重点:keep-alive 只是“缓存实例”,不能替代 Pinia 或路由参数。

5. H5 与 App 宿主协同:不要双写同一份业务事实 ​

WebView 场景下,最容易失控的是:

  • 宿主给了一份登录态 / token / 支付结果 / 引导参数
  • H5 内部又自己从别的接口算了一份
  • 最后两边都能改,状态漂移

更稳的做法是:

  • 宿主负责设备/账号/权限/支付结果等宿主事实
  • H5 负责页面组织、业务展示、局部交互状态
  • 桥接层负责单向注入 / 明确回调 / 幂等处理

常见场景 ​

1. 搜索页筛选条件回退后是否保留 ​

建议拆成:

  • 当前输入过程:组件局部状态
  • 已提交的查询条件:路由 query
  • 搜索结果集缓存:Pinia 或页面缓存策略

2. H5 页面接收宿主登录态 ​

建议拆成:

  • 宿主传账号、token、环境标记
  • Pinia 只收口成只读会话快照
  • 页面不要自己再造一份“推测登录态”

3. 列表 → 详情 → 返回 ​

建议拆成:

  • 当前详情 id:路由参数
  • 列表筛选条件:路由 query + Pinia
  • 列表滚动位置:页面缓存 / keep-alive / 宿主容器能力

常见坑 ​

坑现象修法
组件局部状态一开始就全丢 Piniastore 膨胀、订阅噪音高先判断是否真的跨页面共享
可恢复状态不进 URL刷新后回不到原状态把页面身份和关键 query 放进路由
把 keep-alive 当成状态管理页面缓存后看似没问题,刷新就丢分清实例缓存与业务状态分层
宿主和 H5 各维护一份登录/模式状态双写漂移、偶发错乱明确宿主为事实源,H5 做消费与映射
query / store / 本地 state 同时写同一字段回退、刷新、切页后状态互相覆盖给每一类状态明确唯一归属层

与相近概念对比 ​

概念更适合处理的问题不该替代什么
ref / reactive局部响应式状态跨页面共享业务状态
Pinia跨页面共享状态刷新可恢复入口
vue-router页面身份、query、恢复路径大对象共享缓存
keep-alive组件实例缓存业务状态分层
宿主桥接参数宿主事实源注入H5 内部局部交互状态

对应实验 / 待补 ​

当前本模块 labs 还没补齐,建议优先补:

  • labs/vue/state-routing-architecture/search-query-vs-store/
  • labs/vue/state-routing-architecture/keepalive-route-cache/
  • labs/shared/state-routing-architecture/webview-host-session-sync/

复习检查题 ​

  1. 为什么说 ref / reactive、Pinia、路由参数不是一个层级的问题?

    答:ref / reactive 解决的是单个组件内部的响应式数据绑定;Pinia 解决的是跨组件/全应用级别的内存状态共享;路由参数(Query/Params)解决的是可 URL 化、支持刷新/分享/持久恢复的页面入口状态。三者属于组件级、共享内存级与路由恢复级的不同抽象维度。

  2. 为什么“能刷新恢复”的状态通常不该只放 Pinia?

    答:因为 Pinia 是存放在 JavaScript 运行内存中的状态容器。一旦用户刷新浏览器页面或 WebView 被宿主重建,内存中的 Pinia 实例会被完全重置。依赖刷新恢复的状态(如分页页码、搜索关键词、筛选条件)应同步写在 URL query 中。

  3. keep-alive 为什么不能替代状态架构设计?

    答:keep-alive 只是组件 DOM/实例级别的内存缓存机制,适合保存临时滚动位置或草稿;它无法解决深度深链跳转、跨页面状态同步、持久化恢复及资源回收治理,过度依赖会导致内存过大和状态陈旧问题。

  4. WebView 场景下,宿主与 H5 为什么必须避免双写同一份业务事实?

    答:因为跨端 Bridge 通信具有异步延迟与状态不同步风险,双写会导致“两端数据冲突”或“状态不一致”灾难。工程上应明确单一事实源(如 Token、用户信息由 Native 宿主只读注入,H5 不私自维护副本)。

站点构建时间:2026/8/24 23:43:17