Vue 采用数据驱动的开发模式,大多数情况下我们只需要修改数据,Vue 会自动完成 DOM 的更新。不过,在获取焦点、读取元素尺寸、操作第三方库等场景中,仍然需要直接访问原生 DOM。那么 Vue 提供了什么机制来实现这一能力呢?
ref属性
在 Vue 中,访问和操作原生 DOM 的标准方式是使用 ref。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3 Refs Demo</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
<style>
.box {
width: 200px;
height: 100px;
background-color: lightblue;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
transition: all 0.3s;
}
</style>
</head>
<body>
<div id="root"></div>
<template id="boxTemplate">
<div>
<div ref="myBox" class="box">
原来的文字:{{ message }}
</div>
<button @click="changeDOM">点击直接修改 DOM</button>
</div>
</template>
</body>
<script>
// 使用 Options API 的形式创建应用
Vue.createApp({
data() {
return {
message: "Hello Vue!"
}
},
// 挂载完成后的生命周期钩子
mounted() {
// 组件刚挂载时,也可以像你刚才那样直接修改
console.log("初始 DOM 节点:", this.$refs.myBox);
},
methods: {
changeDOM() {
// 2. 通过 this.$refs.myBox 直接操作原生 DOM 的样式和内容
const boxElement = this.$refs.myBox;
boxElement.style.backgroundColor = 'lightcoral';
boxElement.style.color = 'white';
boxElement.innerHTML = '<b>DOM 被暴力修改了!</b>';
// 打印出修改后的结果
console.log('修改成功:', boxElement.innerHTML);
}
},
template: "#boxTemplate"
}).mount('#root')
</script>
</html>在 Vue 的 Options API 中,我们通常会在需要访问的组件或元素上绑定 ref 属性,然后通过 this.$refs.xxx 获取对应的 DOM 引用,进而调用原生 DOM API 完成各种操作。
ref操作组件
在 Vue 中,ref 不仅可以用来获取 DOM 元素,还可以用来获取子组件的实例。通过这个实例,我们可以直接调用子组件的方法或访问其内部状态,从而实现对组件的操作。
接下来,我们来看一个通过 ref 操作组件的示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3 Component Ref Demo</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
<style>
.child-box {
border: 2px dashed #42b983;
padding: 15px;
margin: 15px 0;
border-radius: 8px;
}
</style>
</head>
<body>
<div id="root"></div>
<template id="parentTemplate">
<div>
<h3>我是父组件</h3>
<counter-item ref="counterComponent"></counter-item>
<button @click="invokeChildAdd">通过父组件让子组件 +1</button>
<button @click="invokeChildReset">通过父组件让子组件重置</button>
</div>
</template>
</body>
<script>
var app = Vue.createApp({
// 父组件的挂载钩子
mounted() {
// 刚挂载时,打印一下看看子组件实例长什么样
console.log("子组件实例:", this.$refs.counterComponent);
},
methods: {
invokeChildAdd() {
// 2. 直接调用子组件里的 increment 方法
this.$refs.counterComponent.increment();
},
invokeChildReset() {
// 直接调用子组件里的 reset 方法
this.$refs.counterComponent.reset();
}
},
template: "#parentTemplate"
});
app.component('counter-item', {
data() {
return {
childCount: 10// 子组件内部的数据
}
},
methods: {
increment() {
this.childCount++;
},
reset() {
this.childCount = 10;
alert('子组件已被父组件重置!');
}
},
// 子组件的模版
template: `
<div class="child-box">
<h4>我是子组件(独立作用域)</h4>
<p>当前子组件内的数字:<b>{{ childCount }}</b></p>
</div>
`
});
app.mount('#root');
</script>
</html>在这个例子中,父组件通过 ref 获取子组件实例,并调用其方法,实现对子组件计数器的增加和重置操作。