Dialog组件状态建模规则
本文所说的组件状态建模规则,特别适用于:Dialog 生命周期长、渲染早于数据的组件
核心设计目标
UI 状态建模(template)的第一目标不是语义最精确,而是结构稳定、可渲染、可推导
简单说,template绑定的变量初始值不能为undefined或者null,最好是预定义的空模板。
二、基础概念划分(这是地基)
区分三种“状态层级”
| 层级 | 例子 | 规则 |
|---|---|---|
| UI 结构状态 | 表单字段、列表项、dialog 内容 | 必须结构稳定 |
| UI 行为状态 | visible / loading / disabled | 可 boolean / enum |
| 业务数据状态 | 接口返回对象 | 可 null / undefined |
template建模只会和UI结构状态和行为状态有关,和业务数据状态无关。
三、最重要的规则(90% 的坑在这里)
规则 1:**template 绑定的数据,禁止null,推荐属性确定的空数据结构
不推荐
const element = ref(null)
{{ element.id }}
推荐
const element = ref({
id: '',
name: '',
})
理由不是“防报错”这么简单,而是:
render / computed / watch(immediate)
会在“业务数据尚未准备好”之前运行
规则 2:null 表示“概念不存在”,而 UI 中很少真的“不存在”
| 状态 | 推荐建模 |
|---|---|
| UI状态尚未准备好 | 空的属性确定的数据结构 |
| 业务对象不存在 | null |
| 接口失败 | error state |
四、关于 computed / watch 的建模规则
规则 3:template绑定的computed = 一开始就要有稳定的数据结构
computed从undefined或者null变化为{id:'xxx'},这就称作不稳定
// 不稳定
const id = computed(() => props.element.id)
稳定方案一(首选)
props.element = { id: '' }
稳定方案二(兜底)
const id = computed(() => props.element?.id ?? '')
方案二是 防御,不是建模优雅
规则 4:watch(immediate) 必须当作“setup 同步代码”对待
watch(
() => props.element,
(el) => {
// 这里 ≈ setup 中直接访问
},
{ immediate: true }
)
所以规则是:
凡是会被 watch(immediate) 读取的数据
都必须在 setup 结束前是安全的
安全的意思是watch的回调函数中需要用guard子句排除到props.element是undefined或者null这种情况。不然会报错。
规则 5:composable 永远假设“调用方是不可靠的”
useSomething(element)
composable 内部必须:
- guard 参数
- 不假设结构存在
- 不信任生命周期顺序
if (!element || !element.id) return
这是 composable 的防御职责
如果你在组件内部写满 if (!xxx) return
那说明状态模型有问题
规则 6:弹框类组件 = 提前存在,延后可见
visible = false // 控制显示
element = {id:"", ...} // 内容占位
不要用 visible = false 的同时element=ref(null)
这里又一次说明null和空数据结构的区别:null表示不存在,空数据结构表示存在,但内容未准备好。不存在的就不能正常渲染,空的数据结构是可以正常渲染的。