watchEffect 在组件挂载前执行,因其在 setup() 同步阶段立即运行,此时 DOM 未生成、template ref 为 null;安全访问需加空值判断并配合 nextTick、onMounted 或 watch(flush: 'post')。

watchEffect 在组件挂载前执行,是因为它在 setup() 同步执行时立即运行一次 —— 此时 DOM 还未生成,ref 绑定的元素(如 elRef)自然为 null,直接访问会报错或逻辑失效。
watchEffect 的执行时机本质
它不是“等组件准备好再监听”,而是“定义即执行”:只要响应式依赖被读取,就立刻触发回调。setup() 中的代码属于同步初始化阶段,此时模板尚未编译、真实 DOM 未挂载,template ref(如 <div ref="el">)对应的值仍是 null。
常见错误写法与风险
- 在
watchEffect回调里直接操作elRef.value?.focus()或elRef.value?.getBoundingClientRect() - 未加空值判断,导致
Cannot read property 'focus' of null - 误以为
onMounted已触发,实际watchEffect比它早得多
安全访问 DOM 的三种方式
✅ 推荐方案一:加空值守卫 + nextTick
确保 DOM 渲染完成后再操作:
watchEffect(() => {
if (elRef.value) {
nextTick(() => {
elRef.value?.focus()
})
}
})
✅ 推荐方案二:用 onMounted + watchEffect 组合
只在挂载后启用监听,避免首次无效执行:
onMounted(() => {
watchEffect(() => {
elRef.value?.focus() // 此时 elRef 必然存在
})
})
✅ 推荐方案三:用 watch + flush: 'post'
延迟到 DOM 更新后执行,适合依赖 props 或响应式数据变化后操作 DOM:
watch(
() => props.open,
(val) => {
if (val && elRef.value) {
elRef.value.focus()
}
},
{ flush: 'post' }
)
关键细节提醒
-
watchEffect的首次执行无法跳过,但可以控制内部逻辑是否执行 -
nextTick不是“等下次渲染”,而是“等本次 DOM 更新完成后执行”,对 ref 赋值后的首次可用性最可靠 - 不要在
watchEffect里反复调用nextTick,容易引发无限循环(尤其当操作又触发响应式更新时)

















