Introduction
If you're used to Vue 2, you might remember that every component's template needed a single root element. In Vue 3, that's no longer necessary because of fragments. This means your components can now have multiple root elements without needing a wrapper.
<!-- Vue 2 -->
<template>
<div> <!-- wrapper 😫 -->
<h1>My Blog Post</h1>
<ArticleComponent>{{ content }}</ArticleComponent>
</div>
</template>
<!-- Vue 3 -->
<template>
<h1>My Blog Post</h1>
<ArticleComponent>{{ content }}</ArticleComponent>
</template>
That's very similar to Fragment in React. However, Vue handles fragments behind the scenes. In fact, in Vue 3, you can think of the <template> tag as a fragment.
The ref() Problem
In Vue 2, we could easily set a ref on a child component, and it would refer to both the wrapper element and the component instance.
But in Vue 3, when there’s no wrapper element, what does the ref refer to? 🤔
If the child component uses the
Options APIor doesn't use<script setup>, the ref will point to the child component's this, giving the parent full access to its properties and methods.
What if we use <script setup> ?
Components using
<script setup>are private by default. To expose properties, we need to use thedefineExposemacro.
Access To Children's Element
- This is what happen when you have wrapper (single root) element:
<!-- Child -->
<template>
<div class="wrapper"> <!-- Root -->
<h1>My Blog Post</h1>
<ArticleComponent>{{ content }}</ArticleComponent>
</div>
</template>
<!-- Parent -->
<script setup lang="ts">
const childRef = ref()
onMounted(()=>{
console.log(childRef.value.$el); // <div class="wrapper">…</div> // [!code highlight]
})
</script>
<template>
<Child ref="childRef" />
</template>
- And when you have more than one root:
<!-- Child -->
<template>
<h1>My Blog Post</h1> <!-- Root 1 -->
<ArticleComponent>{{ content }}</ArticleComponent> <!-- Root 2 -->
</template>
<!-- Parent -->
<script setup lang="ts">
const childRef = ref()
onMounted(()=>{
console.log(childRef.value.$el); // #text
})
</script>
<template>
<Child ref="childRef" />
</template>
Wait, what, what happened?
When we using Fragment(multiple nodes), Vue creates a text node that wraps our child component root nodes.
When using Fragments in Vue 3, Vue inserts an empty text node at the beginning of the component as a marker, which is why $el returns a #text node.
#textis like a reference point that Vue uses internally.
Also I should mention that you have still access to component instance (if you don't use <script setup> in child).
Solution
1) Use Single Root Like this
2) Use Template Refs + defineExpose
Using Template Refs + defineExpose
<!-- Child -->
<script setup lang="ts">
import { ref } from 'vue';
const h1Ref = ref()
const articleRef = ref()
defineExpose({
h1Ref,
articleRef
})
</script>
<template>
<h1 ref="h1Ref">My Blog Post</h1>
<ArticleComponent ref="articleRef">{{ content }}</ArticleComponent>
</template>
<!-- Parent -->
<script setup lang="ts">
const childRef = ref()
onMounted(()=>{
console.log(childRef.value);
// {h1Ref: RefImpl, articleRef: RefImpl}
})
</script>
<template>
<Child ref="childRef" />
</template>
Now you have access to your refs and all the things that you exposed by using defineExpose.
Top comments (0)