Appearance
Vue 状态、路由与宿主协同
一句话定位
这一页不是把 ref / reactive / Pinia / vue-router 当成零散 API 罗列,而是把它们放回一个统一判断:H5 页面里的局部响应式状态、跨页面共享状态、路由状态、宿主协同状态,到底分别该挂在哪一层。
为什么需要
如果只会背 API,很容易在真实项目里出现这些问题:
- 一个筛选条件既存在组件本地,又存在 Pinia,又写进 URL,最后三份状态互相覆盖
vue-router跳页后状态恢复逻辑混乱,不知道该信页面缓存、Pinia 还是 query- WebView 宿主传入登录态、token、灰度参数后,H5 内部又自己维护一套,导致双写漂移
- 页面回退后以为是“状态丢了”,其实只是组件被卸载了,或者缓存策略没配对
所以这一页的重点不是“Vue 怎么写响应式”,而是:
- 局部状态 放组件
- 业务共享状态 放 Pinia
- 可链接、可恢复、可分享的状态 放路由参数
- 宿主注入状态 放桥接边界,不要和页面内部状态混写
先建立哪一层心智
建议先把 Vue / H5 里的状态拆成四层:
- 组件局部状态:输入框内容、loading、展开收起、tab 临时选中态
- 页面级共享状态:列表筛选、会话上下文、用户信息、实验开关
- 路由状态:页面身份、query、返回路径、恢复入口
- 宿主协同状态:登录态、设备信息、支付结果、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 / 宿主容器能力
常见坑
| 坑 | 现象 | 修法 |
|---|---|---|
| 组件局部状态一开始就全丢 Pinia | store 膨胀、订阅噪音高 | 先判断是否真的跨页面共享 |
| 可恢复状态不进 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/
复习检查题
为什么说
ref / reactive、Pinia、路由参数不是一个层级的问题?答:
ref / reactive解决的是单个组件内部的响应式数据绑定;Pinia 解决的是跨组件/全应用级别的内存状态共享;路由参数(Query/Params)解决的是可 URL 化、支持刷新/分享/持久恢复的页面入口状态。三者属于组件级、共享内存级与路由恢复级的不同抽象维度。为什么“能刷新恢复”的状态通常不该只放 Pinia?
答:因为 Pinia 是存放在 JavaScript 运行内存中的状态容器。一旦用户刷新浏览器页面或 WebView 被宿主重建,内存中的 Pinia 实例会被完全重置。依赖刷新恢复的状态(如分页页码、搜索关键词、筛选条件)应同步写在 URL query 中。
keep-alive 为什么不能替代状态架构设计?
答:
keep-alive只是组件 DOM/实例级别的内存缓存机制,适合保存临时滚动位置或草稿;它无法解决深度深链跳转、跨页面状态同步、持久化恢复及资源回收治理,过度依赖会导致内存过大和状态陈旧问题。WebView 场景下,宿主与 H5 为什么必须避免双写同一份业务事实?
答:因为跨端 Bridge 通信具有异步延迟与状态不同步风险,双写会导致“两端数据冲突”或“状态不一致”灾难。工程上应明确单一事实源(如 Token、用户信息由 Native 宿主只读注入,H5 不私自维护副本)。