Dialog组件状态建模规则

18 阅读2分钟

本文所说的组件状态建模规则,特别适用于: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表示不存在,空数据结构表示存在,但内容未准备好。不存在的就不能正常渲染,空的数据结构是可以正常渲染的。

相关知识

vue组件首次渲染执行任务顺序 vue列表渲染设计决策