Vue使用$refs来访问组件实例或DOM元素的注意事项说明
作者:前端布洛芬
在 Vue 项目里,$refs
是个超实用的工具,它能让你直接访问组件实例或者 DOM 元素。不过使用的时候,有一些地方可得注意,下面咱就详细唠唠。
$refs只有在组件渲染完成后才可用
在 Vue 里,组件从创建到渲染完成是有个过程的。只有当组件完全渲染好了,$refs
才能正常使用。要是在组件还没渲染好的时候就想用 $refs
去访问东西,那肯定会出问题。所以,通常会把使用 $refs
的代码放到 mounted
钩子函数里,因为这个钩子函数是在组件渲染完成后才执行的。
export default { // 组件挂载完成后执行的钩子函数 mounted() { // 这里可以安全地使用 $refs 访问组件实例或 DOM 元素 this.$refs.myComponent.someMethod(); // 调用组件实例的方法 this.$refs.myElement.focus(); // 让 DOM 元素获取焦点 } };
不要在模板里直接使用$refs
虽然 $refs
能让你访问组件实例或者 DOM 元素,但千万别在模板里直接用它。因为模板里的代码会在每次数据更新的时候重新计算,如果在模板里用 $refs
,可能会导致意外的结果,而且还会影响性能。
<template> <!-- 不要这样做 --> <!-- <div>{{ $refs.myElement.textContent }}</div> --> <div ref="myElement">这是一个 DOM 元素</div> </template> <script> export default { mounted() { // 在 mounted 钩子函数里使用 $refs const text = this.$refs.myElement.textContent; console.log(text); // 输出: 这是一个 DOM 元素 } }; </script>
$refs不是响应式的
$refs
不像 Vue 的响应式数据那样,数据一变页面就跟着更新。$refs
只是一个普通的对象,它的属性值在组件渲染完成后就固定了。所以,如果你想在数据变化的时候更新 $refs
相关的操作,就得手动去处理。
<template> <div> <button @click="updateElement">更新元素</button> <div ref="myElement">{{ message }}</div> </div> </template> <script> export default { data() { return { message: '初始消息' }; }, methods: { updateElement() { this.message = '更新后的消息'; // 手动更新 $refs 相关的操作 this.$refs.myElement.textContent = this.message; } } }; </script>
动态绑定ref时要注意
要是你需要动态绑定 ref
,也就是根据不同的条件给不同的元素或者组件绑定 ref
,那得小心了。因为动态绑定 ref
可能会导致 $refs
的值发生变化,所以在使用的时候要确保 $refs
里确实有你想要的元素或者组件。
<template> <div> <!-- 动态绑定 ref --> <component :is="currentComponent" :ref="currentRef"></component> <button @click="switchComponent">切换组件</button> </div> </template> <script> import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; export default { data() { return { currentComponent: ComponentA, currentRef: 'componentRef', componentRef: null }; }, methods: { switchComponent() { this.currentComponent = this.currentComponent === ComponentA ? ComponentB : ComponentA; // 切换组件后,确保 $refs 里有正确的组件实例 if (this.$refs.componentRef) { this.$refs.componentRef.someMethod(); } } } }; </script>
在子组件销毁时清理$refs
当子组件被销毁的时候,$refs
里对应的引用不会自动清除。所以,要是你在子组件销毁后还去访问 $refs
里的这个引用,就会报错。为了避免这种情况,你可以在子组件销毁的时候手动清理 $refs
里的引用。
<template> <div> <child-component ref="childRef"></child-component> <button @click="destroyChild">销毁子组件</button> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { destroyChild() { // 销毁子组件 this.$refs.childRef.$destroy(); // 手动清理 $refs 里的引用 this.$refs.childRef = null; } } }; </script>
总之,$refs
是个很强大的工具,但使用的时候得注意上面这些点,这样才能避免出现各种奇怪的问题,让你的 Vue 项目更加稳定。
除了$refs,Vue还有哪些可以访问组件实例或DOM元素的方式?
在 Vue 里,除了使用 $refs
访问组件实例或 DOM 元素外,还有以下几种方式:
通过事件机制传递信息
在 Vue 中,你可以利用自定义事件和事件总线在组件间传递数据,从而间接访问组件实例。
自定义事件
子组件能够通过 $emit
触发自定义事件,把数据传递给父组件,父组件在接收到事件后就可以访问子组件实例的属性或方法。
<template> <!-- 父组件 --> <div> <child-component @custom-event="handleCustomEvent"></child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { handleCustomEvent(childInstance) { // 访问子组件实例 console.log(childInstance.someMethod()); } } }; </script>
<template> <!-- 子组件 --> <div> <button @click="sendInstance">发送实例</button> </div> </template> <script> export default { methods: { sendInstance() { // 触发自定义事件,传递当前组件实例 this.$emit('custom-event', this); }, someMethod() { return '这是子组件的方法'; } } }; </script>
事件总线
事件总线是一个全局的事件中心,组件能够在上面触发和监听事件,以此实现组件间的通信。
// eventBus.js import Vue from 'vue'; export const eventBus = new Vue();
<template> <!-- 发送组件 --> <div> <button @click="sendMessage">发送消息</button> </div> </template> <script> import { eventBus } from './eventBus.js'; export default { methods: { sendMessage() { // 触发事件总线的事件 eventBus.$emit('message-sent', this); } } }; </script>
<template> <!-- 接收组件 --> <div></div> </template> <script> import { eventBus } from './eventBus.js'; export default { mounted() { // 监听事件总线的事件 eventBus.$on('message-sent', (senderInstance) => { console.log(senderInstance.someMethod()); }); } }; </script>
使用provide和inject
provide
和 inject
主要用于实现跨级组件间的通信,父组件能够通过 provide
提供数据,子组件可以使用 inject
注入这些数据。
<template> <!-- 父组件 --> <div> <child-component></child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, provide() { return { parentInstance: this }; } }; </script>
<template> <!-- 子组件 --> <div></div> </template> <script> export default { inject: ['parentInstance'], mounted() { // 访问父组件实例 console.log(this.parentInstance.someMethod()); } }; </script>
使用Vue.observable(Vue 2)或reactive(Vue 3)
在 Vue 2 里可以使用 Vue.observable
创建一个响应式对象,在 Vue 3 中则使用 reactive
。通过这个响应式对象存储组件实例,从而实现对组件实例的访问。
Vue 2
// store.js import Vue from 'vue'; export const store = Vue.observable({ componentInstance: null });
<template> <!-- 组件 A --> <div> <button @click="setInstance">设置实例</button> </div> </template> <script> import { store } from './store.js'; export default { methods: { setInstance() { // 将当前组件实例存储到响应式对象中 store.componentInstance = this; } } }; </script>
<template> <!-- 组件 B --> <div></div> </template> <script> import { store } from './store.js'; export default { mounted() { // 访问存储的组件实例 if (store.componentInstance) { console.log(store.componentInstance.someMethod()); } } }; </script>
Vue 3
// store.js import { reactive } from 'vue'; export const store = reactive({ componentInstance: null });
<template> <!-- 组件 A --> <div> <button @click="setInstance">设置实例</button> </div> </template> <script setup> import { store } from './store.js'; const setInstance = () => { // 将当前组件实例存储到响应式对象中 store.componentInstance = getCurrentInstance(); }; </script>
<template> <!-- 组件 B --> <div></div> </template> <script setup> import { store } from './store.js'; if (store.componentInstance) { console.log(store.componentInstance.someMethod()); } </script>
这些方法都有各自的适用场景,你可以根据具体需求来选择合适的方式。
在Vue中,如何避免$refs访问带来的性能问题?
在 Vue 里使用 $refs
访问组件实例或 DOM 元素时,若使用不当可能会引发性能问题。以下是一些避免 $refs
访问带来性能问题的方法:
避免在渲染循环中频繁访问$refs
$refs
本身并非响应式的,但在渲染循环里频繁访问 $refs
会导致不必要的计算和 DOM 操作,从而降低性能。应该把 $refs
的访问逻辑移到事件处理函数或者生命周期钩子函数中。
<template> <div> <div ref="myElement">示例元素</div> <button @click="handleClick">点击</button> </div> </template> <script> export default { methods: { handleClick() { // 在事件处理函数中访问 $refs const element = this.$refs.myElement; if (element) { // 对元素进行操作 element.style.color = 'red'; } } } }; </script>
仅在必要时使用$refs
$refs
主要用于直接访问组件实例或 DOM 元素,不过很多时候可以借助 Vue 的响应式系统来实现相同的功能,从而避免使用 $refs
。
示例:动态改变样式
不使用 $refs
的情况:
<template> <div> <div :style="{ color: textColor }">示例元素</div> <button @click="changeColor">改变颜色</button> </div> </template> <script> export default { data() { return { textColor: 'black' }; }, methods: { changeColor() { this.textColor = 'red'; } } }; </script>
及时清理不再使用的$refs
当组件被销毁时,$refs
里对应的引用不会自动清除。若不清理,可能会造成内存泄漏。所以在组件销毁时要手动清理 $refs
。
<template> <div> <child-component ref="childRef"></child-component> <button @click="destroyChild">销毁子组件</button> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { destroyChild() { // 销毁子组件 this.$refs.childRef.$destroy(); // 手动清理 $refs 里的引用 this.$refs.childRef = null; } } }; </script>
避免在watch中频繁访问$refs
watch
用于监听数据变化,若在 watch
里频繁访问 $refs
,会导致不必要的性能开销。可以在 watch
中设置 immediate: false
,避免初始化时就执行访问操作。
<template> <div> <div ref="myElement">示例元素</div> <input v-model="inputValue" /> </div> </template> <script> export default { data() { return { inputValue: '' }; }, watch: { inputValue: { handler(newValue) { if (this.$refs.myElement) { // 对元素进行操作 this.$refs.myElement.textContent = newValue; } }, immediate: false // 避免初始化时执行 } } }; </script>
利用缓存机制
要是需要多次访问 $refs
,可以把访问结果缓存起来,避免重复访问。
<template> <div> <div ref="myElement">示例元素</div> <button @click="doSomething">执行操作</button> <button @click="doAnotherThing">执行另一个操作</button> </div> </template> <script> export default { data() { return { cachedElement: null }; }, methods: { getElement() { if (!this.cachedElement) { this.cachedElement = this.$refs.myElement; } return this.cachedElement; }, doSomething() { const element = this.getElement(); if (element) { // 对元素进行操作 element.style.fontSize = '20px'; } }, doAnotherThing() { const element = this.getElement(); if (element) { // 对元素进行另一个操作 element.style.backgroundColor = 'yellow'; } } } }; </script>
通过以上这些方法,可以有效避免 $refs
访问带来的性能问题,提升 Vue 应用的性能和稳定性。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。