在 Vue 框架的日常开发中,
$refs 是一个高频出现但又容易被误用的 API。它为开发者提供了直接操作 DOM 元素或组件实例的便捷方式,但如果使用不当,不仅会违背 Vue 的设计理念,还可能引发各种难以排查的问题。本文将从核心作用、使用场景、注意事项三个维度,带你全面掌握$refs的正确打开方式。一、$refs 的核心作用
$refs 是 Vue 实例上的一个对象属性,其核心作用是提供对 DOM 元素或子组件实例的直接访问,打破 Vue “数据驱动视图” 的封装性,满足特定场景下的手动操作需求。1. 访问 DOM 元素
在 Vue 的模板语法中,我们通常通过
v-bind、v-model等指令实现数据与视图的双向绑定,无需直接操作 DOM。但在某些场景下(如获取元素尺寸、手动聚焦输入框、调用 DOM 原生方法),直接操作 DOM 是最高效的方式,此时$refs就是最优选择。示例代码:
vue
<template>
<div>
<!-- 给DOM元素添加ref属性 -->
<input ref="inputBox" type="text" placeholder="请输入内容" />
<button @click="focusInput">聚焦输入框</button>
</div>
</template>
<script>
export default {
methods: {
focusInput() {
// 通过this.$refs.xxx访问DOM元素,调用原生focus方法
this.$refs.inputBox.focus();
}
}
}
</script>
2. 访问子组件实例
除了 DOM 元素,
$refs还能直接获取子组件的实例,从而调用子组件的方法、访问子组件的 data 或 props(不推荐直接修改 props,违背单向数据流原则)。示例代码:
vue
<!-- 子组件 Child.vue -->
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
}
}
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<div>
<Child ref="childComp" />
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: { Child },
methods: {
callChildMethod() {
// 访问子组件实例,调用其方法
this.$refs.childComp.increment();
}
}
}
</script>
二、使用 $refs 必须注意的 8 个关键点
$refs虽然便捷,但使用时需严格遵守 Vue 的运行机制,否则极易出现 “refs 为空”“操作无效” 等问题。1. $refs 不是响应式的
这是
$refs最核心的特性,也是最容易踩坑的点。Vue 不会对$refs做响应式处理,当依赖的数据源变化导致 DOM 重新渲染时,$refs的更新不会触发组件的重新渲染,也无法通过 watch 监听$refs的变化。错误示例(试图监听 refs 变化):
javascript
运行
// 无效代码!$refs非响应式,watch不会触发
watch: {
'$refs.inputBox'(newVal) {
console.log('输入框DOM变化', newVal);
}
}
2. 初始化时机:DOM 渲染完成后才可用
$refs的赋值发生在 DOM 渲染完成之后,因此在created钩子中无法访问$refs(此时 DOM 尚未生成),只能在mounted(挂载完成)或后续生命周期中使用。正确 / 错误对比:
javascript
运行
export default {
created() {
// 错误:此时DOM未渲染,$refs.inputBox为undefined
console.log(this.$refs.inputBox); // undefined
},
mounted() {
// 正确:mounted阶段DOM已挂载完成
console.log(this.$refs.inputBox); // <input> DOM元素
}
}
3. 动态渲染场景:nextTick 的使用
当通过条件渲染(
v-if)、数据更新等方式触发 DOM 重新渲染时,$refs的更新会延迟到下一个 DOM 更新周期。此时需要使用this.$nextTick()等待 DOM 更新完成后再访问$refs。示例(v-if 切换后访问 refs):
vue
<template>
<div>
<input v-if="showInput" ref="dynamicInput" />
<button @click="toggleInput">显示/隐藏输入框</button>
</div>
</template>
<script>
export default {
data() {
return {
showInput: false
}
},
methods: {
toggleInput() {
this.showInput = !this.showInput;
// 直接访问会获取到更新前的refs(可能为undefined)
console.log(this.$refs.dynamicInput); // 第一次点击时为undefined
// 正确做法:通过nextTick等待DOM更新
this.$nextTick(() => {
if (this.showInput) {
this.$refs.dynamicInput.focus(); // 成功聚焦
}
});
}
}
}
</script>
4. 循环渲染中的 refs:返回数组而非单个元素
当
ref属性用在v-for循环中时,$refs.xxx会返回一个数组,数组元素为对应位置的 DOM 元素 / 组件实例,顺序与循环数据一致。示例(v-for 中的 refs):
vue
<template>
<div>
<div v-for="(item, index) in list" :key="index" ref="itemBox">
{{ item }}
</div>
<button @click="getRefsInLoop">获取循环refs</button>
</div>
</template>
<script>
export default {
data() {
return {
list: ['A', 'B', 'C']
}
},
methods: {
getRefsInLoop() {
console.log(this.$refs.itemBox); // [div, div, div] 数组
// 访问第二个元素
console.log(this.$refs.itemBox[1].innerText); // B
}
}
}
</script>
5. 避免过度使用:优先遵循数据驱动
Vue 的核心设计理念是 “数据驱动视图”,
$refs仅应作为 “兜底方案” 使用。以下场景应优先避免使用$refs:- 替代
v-model直接修改输入框 value:应通过绑定 data 实现; - 替代
v-show/v-if控制元素显示隐藏:应通过修改数据驱动; - 替代 props/emit 实现父子组件通信:违背单向数据流原则。
6. 组件 refs:仅能访问非递归的子组件
如果子组件是递归组件(自身调用自身),
$refs只能获取到最外层的组件实例;如果需要访问深层嵌套的子组件,建议通过provide/inject或事件总线实现,而非链式访问$refs(如this.$refs.parent.$refs.child,耦合性过高)。7. 服务器端渲染 (SSR):慎用 $refs
在 SSR 场景下,服务端没有 DOM 环境,
$refs会返回空值。如果代码中强依赖$refs,会导致服务端渲染失败,此时需通过process.client判断环境,仅在客户端执行 refs 相关操作。8. 命名规范:避免与 Vue 内置属性冲突
$refs的命名不要使用 Vue 实例的内置属性名(如$el、$data、$options等),否则会覆盖内置属性,引发不可预知的问题。错误示例:
vue
<!-- 错误:ref命名为$el,覆盖Vue内置的$el属性 -->
<div ref="$el">测试</div>
三、$refs 的适用场景总结
虽然
$refs有诸多限制,但在以下场景中使用是合理且高效的:- 操作 DOM 原生方法:如聚焦输入框、获取元素尺寸 / 位置、触发滚动等;
- 调用子组件的方法:如子组件封装了复杂的业务逻辑,需要父组件主动触发;
- 第三方库集成:如某些 jQuery 插件、图表库需要直接绑定 DOM 元素。
总结
$refs的核心作用是直接访问 DOM 元素 / 子组件实例,是 Vue “数据驱动” 理念的补充,而非替代;- 使用
$refs的核心注意点:非响应式、DOM 渲染后才可用、动态场景需配合nextTick、循环中返回数组; - 遵循 “最小使用原则”,优先通过数据驱动实现功能,仅在必要场景下使用
$refs,避免代码耦合性过高。
掌握
$refs的正确用法,既能发挥其便捷性,又能保持 Vue 代码的优雅与可维护性。希望本文能帮助你避开$refs的常见陷阱,写出更符合 Vue 设计理念的代码。