虽然vuejs的极大程度的减少了对dom的操作,定义了prop 和事件,但有时你可能仍然需要直接访问 JavaScript 中的子组件。此时可使用ref为子组件或 HTML 元素指定引用 ID。接下来通过几个简单的示例,演示如果使用vue3.2的script setup语法糖进行模板引用。

一、ref简单使用

获取模板内button元素,并在控制台打印。

1. 示例代码

<template>
  <button ref="button">这是一个按钮</button>
</template>
<script setup>
import {ref, onMounted} from 'vue'

const button = ref(null)
onMounted(() => {
  // DOM元素将在初始渲染后分配给ref
  console.log(button.value)
})
</script>

<style scoped lang="scss">
</style>

2. 执行结果

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2308212/1630416891039-79cc98be-ea4e-4f59-8af1-9c221ca2c200.png#clientId=ub22ec5e5-1058-4&from=paste&height=50&id=u8a757ace&margin=[object Object]&name=image.png&originHeight=50&originWidth=552&originalType=binary&ratio=1&size=4820&status=done&style=none&taskId=u49dd8a70-7068-4d70-b631-6f29b01b2a5&width=552)

二、父组件获取子组件变量

1. 父组件(Test.vue)

<template>
  <div>
    <Demo ref="child"></Demo>
    <br/>
    父组件获取到子组件数据——>{{ childValue }}
    <br/>
    <button @click="getChildValue">获取子组件数据</button>
  </div>
</template>

<script setup>
import {ref} from "vue";
import Demo from "@/components/Demo.vue";
const child = ref(null);
const childValue = ref()
const getChildValue = () => {
  childValue.value = child.value.count
};
</script>

<style scoped>

</style>

2. 子组件(Demo.vue)

<template>
  <div>子组件计数:{{ count }}</div>
</template>

<script setup>
import {ref} from "vue";
const count = ref(10);
defineExpose({
  count
})
</script>

<style scoped>

</style>

3. 执行结果

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2308212/1630418184127-2550d9e6-32b6-4a53-a9aa-a082c824523d.png#clientId=ub22ec5e5-1058-4&from=paste&height=99&id=u3cb7857f&margin=[object Object]&name=image.png&originHeight=99&originWidth=258&originalType=binary&ratio=1&size=8713&status=done&style=none&taskId=u5676901f-b39b-495a-8a0e-0b0da7259df&width=258)

4. 注意事项

  • 因为是调用组件的变量和方法。因此要给组件添加ref,而不是外层的html标签。
  • 使用 <script setup> 的组件,默认不会暴露。为了在 <script setup> 组件中使用要暴露出去的属性,需要使用 defineExpose声明。

三、父组件执行子组件方法

1. 父组件(Test.vue)

<template>
  <div>
    <Demo ref="child"></Demo>
    <br/>
    父组件获取到子组件数据——>{{ childValue }}
    <br/>
    <button @click="getChildValue">父组件获取子组件数据</button>
    <br/>
    <button @click="childValueAdd">父组件执行子组件事件</button>
  </div>
</template>

<script setup>
import {ref} from "vue";
import Demo from "@/components/Demo.vue";
const child = ref(null);
const childValue = ref()
const getChildValue = () => {
  childValue.value = child.value.count
};
const childValueAdd = () => {
  child.value.add()
}
</script>

<style scoped>

</style>

2. 子组件(Demo.vue)

<template>
  <div>子组件计数:{{ count }}</div>
  <br/>
  <button @click="add">子组件事件:值+1</button>
</template>

<script setup>
import {ref} from "vue";
const count = ref(10);
const add = () => {
  count.value++
}
defineExpose({
  count,
  add
})
</script>

<style scoped>

</style>

3. 执行结果

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2308212/1630418695420-e1e42b8f-0c17-4ef0-a692-9dbb46ebc06d.png#clientId=ub22ec5e5-1058-4&from=paste&height=153&id=u384a332f&margin=[object Object]&name=image.png&originHeight=153&originWidth=279&originalType=binary&ratio=1&size=12798&status=done&style=none&taskId=u4e17edc1-4039-49e8-b352-e5d2316ff28&width=279)

四、组件ref与标签ref区别

1. 组件ref

<template>
  <div>
    <Demo ref="child"></Demo>
  </div>
</template>

<script setup>
import {onMounted, ref} from "vue";
import Demo from "@/components/Demo.vue";

const child = ref(null);
onMounted(() => {
  console.log(child.value)
})
</script>

<style scoped>

</style>

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2308212/1630418905922-2f7f6679-63bb-4454-ade7-997ed4c52ffc.png#clientId=ub22ec5e5-1058-4&from=paste&height=194&id=uaddeaabe&margin=[object Object]&name=image.png&originHeight=194&originWidth=546&originalType=binary&ratio=1&size=28713&status=done&style=none&taskId=ub036570f-c84a-49fa-832e-27197bdd004&width=546)

  • 通过打印结果可知,获取到的结果是组件的变量和方法

2. 标签ref

<template>
  <div ref="child">
    <Demo></Demo>
  </div>
</template>

<script setup>
import {onMounted, ref} from "vue";
import Demo from "@/components/Demo.vue";

const child = ref(null);
onMounted(() => {
  console.log(child.value)
})
</script>

<style scoped>

</style>

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2308212/1630419035463-8e778aa2-4ba2-48e3-9c30-5662324252c7.png#clientId=ub22ec5e5-1058-4&from=paste&height=91&id=u2cd0b07f&margin=[object Object]&name=image.png&originHeight=91&originWidth=541&originalType=binary&ratio=1&size=9539&status=done&style=none&taskId=u3c8b55c1-1f3b-42a1-a91e-64362e8d894&width=541)

  • 通过打印结果可知,获取到的结果是html元素

3. 总结

对比可知,组件添加ref后,打印的是组件的变量和方法;组件外层元素添加ref后,打印的是html节点。 因此,当调用组件的变量和方法时,需要在组件上添加ref;当对组件的dom元素进行操作时,应该在组件外层嵌套标签,并添加ref。

参考链接

  • vue ref模板引用

https://vue3js.cn/docs/zh/guide/composition-api-template-refs.html#%E6%A8%A1%E6%9D%BF%E5%BC%95%E7%94%A8

  • vue3.2 defineexpose

https://vue-docs-next-zh-cn.netlify.app/api/sfc-script-setup.html#defineexpose