问题:

<div v-if="isShow">
	<ScheduleForm ref="formRef" />
</div>

上述这段代码,初始化后,isShow.value=true后,但formRef.value为null,无法使用formRef.value.fill()怎么解决?

这是一个 Vue 3 中常见的 ref 在条件渲染(v-if)下的异步问题。当 isShowfalse 变为 true 时,DOM 更新是异步的,此时立即访问 formRef.value 会得到 null

问题原因

<div v-if="isShow">
  <ScheduleForm ref="formRef" ... />
</div>

isShow.value = true 后:

  1. Vue 的响应式系统检测到变化
  2. 但 DOM 更新是在下一个 tick 中进行的
  3. 子组件 ScheduleForm 的挂载也是异步的
  4. 所以立即访问 formRef.value 得到 null

解决方案

方案 1:使用 nextTick(推荐)

import { ref, nextTick } from 'vue'

const isShow = ref(false)
const formRef = ref(null)

async function openForm() {
  isShow.value = true
  
  // 等待 DOM 更新完成
  await nextTick()
  
  // 现在可以安全访问了
  formRef.value?.fill?.({ /* 数据 */ })
}

方案 2:使用 watch 监听 isShow

import { ref, watch } from 'vue'

const isShow = ref(false)
const formRef = ref(null)

// 监听 isShow 变化,在组件挂载后执行
watch(isShow, async (newVal) => {
  if (newVal) {
    await nextTick()
    formRef.value?.fill?.({ /* 数据 */ })
  }
})

function openForm() {
  isShow.value = true
}

方案 3:使用 v-show 替代 v-if(如果性能允许)

<!-- v-show 只是切换 display,组件始终存在,ref 一直可用 -->
<div v-show="isShow">
  <ScheduleForm ref="formRef" ... />
</div>
function openForm() {
  isShow.value = true
  // 可以立即使用,因为组件一直存在
  formRef.value?.fill?.({ /* 数据 */ })
}

注意:v-show 的缺点是组件始终会被渲染(即使隐藏),如果 ScheduleForm 初始化开销大,不建议使用。

方案 4:在子组件挂载时通过事件通知父组件

<!-- ScheduleForm.vue -->
<script setup>
import { onMounted } from 'vue'

const emit = defineEmits(['mounted'])

onMounted(() => {
  emit('mounted')
})
</script>
<!-- 父组件 -->
<ScheduleForm 
  ref="formRef" 
  @mounted="handleFormMounted"
  ...
/>

<script setup>
function handleFormMounted() {
  // 此时 formRef.value 一定可用
  formRef.value?.fill?.({ /* 数据 */ })
}
</script>

方案 5:使用 shallowRef + 封装打开方法(最优雅)

import { ref, nextTick } from 'vue'

const isShow = ref(false)
const formRef = ref(null)

// 封装一个安全的打开方法
async function openForm(data) {
  isShow.value = true
  await nextTick()
  
  if (!formRef.value) {
    console.warn('ScheduleForm 组件未正确挂载')
    return
  }
  
  formRef.value.fill(data)
}

// 使用
openForm({ name: 'test', date: '2024-01-01' })

推荐选择

场景 推荐方案
简单场景,只在一处调用 方案 1 (nextTick)
需要在打开时自动填充数据 方案 5 (封装方法)
组件初始化开销小,频繁切换 方案 3 (v-show)
复杂的表单交互逻辑 方案 4 (mounted 事件)

最常用的是 方案 1 或 方案 5,它们既保持了 v-if 的性能优势,又解决了 ref 访问时机的问题。