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 API
or 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 thedefineExpose
macro.
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.
#text
is 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)