Vue 组件通信方式选择
Vue 组件通信的决策页:按所有权、拓扑范围、数据语义和生命周期选择 props/emit、slot、v-model、provide/inject 或 Pinia。
#frame
#vue3
#synthesis
#growing
[!info] related notes
- 通信地图: Vue 组件通信方式总览
- 父子契约: props / emit
- 值通道: defineModel vs props/emit
- 跨层上下文: provide / inject
- 全局状态: Pinia
Vue 组件通信方式选择
先问四个问题
不要先问“该用 Pinia 还是 provide/inject”。
先判断:
- 谁拥有这个 state?
- 谁需要读取/修改?
- 通信范围有多大?
- 这是数据、事件、内容结构,还是上下文?
决策 1:直接父子数据
parent owns state
child displays / requests update
优先:
- props 输入;
- emit 输出。
如果本质是标准 value contract,可以继续考虑 v-model / defineModel。
不要为了少写两行 props 就把 state 提升到 Pinia。
决策 2:父级决定一块 UI 内容
问题如果是:
卡片内部这块结构由使用者提供。
优先 slot。
这是 composition,不是 shared state。
决策 3:跨很多中间层共享 context
如果中间组件根本不关心数据,只是祖先和深层后代需要共享:
FormRoot
→ nested fields
优先考虑 provide/inject。
但如果这个 state 已经跨多个独立页面和业务模块,scope 可能已超过 component tree context。
决策 4:业务级共享 state
当 state 同时满足多个信号:
- 多个独立页面依赖;
- 多处更新;
- 需要统一 action/derived state;
- 生命周期超出单个组件树;
- 业务 ownership 明确;
再考虑 Pinia。
决策 5:Server State 不要顺手塞 Pinia
从 API 获取的数据还需要问:
- 谁是 authority?
- freshness 怎么定义?
- refetch/invalidation 怎么做?
- loading/error/retry 谁负责?
Server state 和 client shared state 不是一个问题。
决策树
需要通信?
↓
直接父子数据/事件?
├─ 是 → props / emit
│ └─ 标准双向 value contract → v-model / defineModel
│
└─ 否
↓
父级只是提供 UI 结构?
├─ 是 → slot
└─ 否
↓
同一组件树跨层上下文?
├─ 是 → provide / inject
└─ 否
↓
跨页面/模块的业务级 client state?
├─ 是 → Pinia candidate
└─ 否 → 重新检查 state ownership
常见错误
Global state inflation
本来只有父子需要,却直接放进 Pinia。
结果:
- ownership 模糊;
- cleanup 困难;
- state 生命周期过长。
provide/inject 变成隐式全局变量
provide/inject 应有明确 tree scope 和 contract。
用 props 配置整个页面结构
如果大量 props 只是描述“这里放什么 UI”,可能更适合 slot/composition。
双真值
父级有一份 state,子组件又复制一份长期不同步的 local state。
需要明确 controlled / uncontrolled 边界。
学习检查
- 一个通信选择题最先应该判断 API 还是 state ownership?为什么?
- 什么情况下 props/emit 比 Pinia 更正确,即使写起来更繁琐?
- slot 与 provide/inject 分别解决“结构”和“上下文”中的哪一个?
- provide/inject 什么时候开始变成错误的隐式 global state?
- Server state 为什么不能仅凭“很多组件要用”就塞进 Pinia?
- 给一个三层表单、跨页面登录用户、可编辑 Dialog、可插槽 Card 四个场景,分别完成选择。