在 Vue 项目里,想用 $refs 或 ref 直接操作 DOM 时,最常见的报错三连:

Cannot read property 'style' of undefined
this.$refs.xxx is undefined
<template ref> 拿不到

本文把 6 个高频翻车场景整理成「病历卡」,附「可复制粘贴」的急救代码,3 分钟完成定位 + 修复。


一、$refs 拿不到的根本原因

版本/语法能拿到 DOM 的时机常见坑
Vue2 选项式mounted 之后在 created 就访问
Vue3 <script setup>onMounted 之后用字符串模板 this.$refs
v-if / v-for 动态渲染DOM 真正插入后没等 nextTick
子组件 ref子组件实例渲染后没 defineExpose

二、6 个翻车现场 & 修复

1️⃣ 在 created 里访问 → 直接 undefined

<script>
export default {
  created() {
    console.log(this.$refs.box); // ❌ undefined
  }
}
</script>
<template>
  <div ref="box">Hello</div>
</template>

修复:改到 mounted 或加 nextTick

mounted() {
  this.$nextTick(() => console.log(this.$refs.box)); // ✅
}

2️⃣ <script setup> 仍用 this.$refs

<script setup>
console.log(this.$refs.box); // ❌ 不存在 this
</script>

修复:使用组合式 ref

<script setup>
import { ref, onMounted } from 'vue'
const box = ref(null)
onMounted(() => console.log(box.value)) // ✅
</script>
<template>
  <div ref="box">Hello</div>
</template>

3️⃣ v-if 切换后未等待 DOM

<template>
  <button @click="show = !show">Toggle</button>
  <div v-if="show" ref="panel">Panel</div>
</template>

<script setup>
import { ref, nextTick } from 'vue'
const show = ref(false)
const panel = ref(null)

async function toggle() {
  show.value = false
  await nextTick() // ✅ 等待 DOM 更新
  console.log(panel.value) // null(符合预期)
}
</script>

4️⃣ v-for 里想拿 DOM 数组

<template>
  <ul>
    <li v-for="(item, i) in list" :key="i" :ref="setItemRef">{{ item }}</li>
  </ul>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue'
const list = ref(['A', 'B', 'C'])
const itemRefs = ref([])

const setItemRef = (el) => {
  if (el) itemRefs.value.push(el)
}
onMounted(async () => {
  await nextTick()
  console.log(itemRefs.value) // ✅ [li, li, li]
})
</script>

5️⃣ 子组件实例未暴露方法

<!-- Parent.vue -->
<template>
  <Child ref="child" />
  <button @click="child?.focus()">Focus</button>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const child = ref(null)
</script>

<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const input = ref(null)
function focus() {
  input.value?.focus()
}
defineExpose({ focus }) // ✅ 暴露给父组件
</script>

6️⃣ 动态组件 + ref 指向实例

<template>
  <component :is="comp" ref="dynRef" />
</template>

<script setup>
import { ref, shallowRef, onMounted } from 'vue'
import CompA from './CompA.vue'
import CompB from './CompB.vue'

const comp = shallowRef(CompA)
const dynRef = ref(null)

onMounted(() => {
  console.log(dynRef.value); // ✅ 当前渲染的组件实例
})
</script>

三、排查 checklist(建议打印)

现象快速检查
undefined是否早于 mounted / onMounted
<script setup>是否用了 this.$refs
v-if 后拿不到是否 await nextTick()
子组件方法是否 defineExpose
路径/语法Vue2 vs Vue3 语法混用

四、一句话总结

“拿不到 DOM” 90% 是时机错,9% 是语法混,1% 才是玄学。
牢记:

  • Vue2 等 mounted
  • Vue3 <script setup> 用 ref + onMounted
  • 动态渲染后 nextTick
    照此清单操作,$refs 永不翻车。

最后问候亲爱的朋友们,并邀请你们阅读我的全新著作

📚 《Vue.js 3企业级项目开发实战(微课视频版》

在这里插入图片描述

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐