Vue组件挂载初始化时机疑问:父mounted时子暴露ref为何为null
我有一个子组件Comp.vue,通过defineExpose将内部元素的ref暴露给父组件App.vue,实际场景中需要操作该元素(这里以打印为例)。已知Vue中子组件先于父组件完成挂载,但父组件onMounted钩子中访问myRef时,它仍为null。使用await nextTick()可解决问题,但我不明白为何挂载完成后操作未结束,以及nextTick为何能确保所有操作完成,毕竟这些都是内部异步操作。
组件代码示例
Comp.vue
<script setup> import { ref } from 'vue' const el = ref(null); defineExpose({ inner: el }); </script> <template> <p ref="el">My Element</p> </template>
App.vue
<script setup> import { nextTick, onMounted, ref } from 'vue' import Comp from './Comp.vue' const myRef = ref(null); function logMe() { console.log(myRef.value); } onMounted(async () => { // await nextTick(); <-- 取消注释即可解决问题 logMe(); }); </script> <template> <button @click="logMe">Log the element</button> <Comp :ref="(el) => {myRef = el ? el.inner : undefined}" /> </template>
问题原因解析
挂载流程与ref赋值的异步性
Vue的组件挂载是从子到父执行mounted钩子,但ref的绑定(包括子组件内部元素的ref、父组件通过:ref回调获取子组件暴露的ref)是异步的,属于DOM更新队列的一部分。父组件的onMounted触发时,子组件虽然已经完成挂载,但子组件内部的ref绑定、以及父组件接收子组件暴露的innerref这个过程,还没有被执行——这些操作被放入了微任务队列,要等到当前同步代码执行完才会处理。nextTick的作用
nextTick的本质是将回调函数加入到当前DOM更新周期结束后的微任务队列中。当你调用await nextTick()时,会等待当前所有未完成的DOM更新操作(包括ref的绑定和传递)全部执行完毕,再继续执行后续代码。这时候myRef已经被正确赋值,自然能拿到目标元素的ref。
简单来说:父组件onMounted是自身挂载完成就触发,但ref的赋值是挂载后的异步操作,需要等nextTick才能确保这些操作全部完成。
修正后的代码
将App.vue中的onMounted钩子修改为:
onMounted(async () => { await nextTick(); logMe(); // 此时能正确打印目标元素的ref });
内容的提问来源于stack exchange,提问作者cryfur

