Vue 3: Accessing Child Component Instances with Template Refs
This challenge focuses on a fundamental Vue 3 feature: using template refs to gain direct access to child component instances. Understanding how to do this is crucial for advanced component interaction, imperative DOM manipulation when necessary, and integrating with third-party libraries that require direct DOM or component access.
Problem Description
Your task is to create a parent Vue component that dynamically renders a list of child components. The parent component needs to be able to access the instance of a specific child component when a button within the parent is clicked. This interaction will involve using Vue's template ref system.
Requirements:
-
Parent Component (
ParentComponent):- It should accept an array of data (e.g., strings) as a prop. Each item in this array will represent data for a child component.
- It should render a
ChildComponentfor each item in the data array. - It should display a button labeled "Focus First Child".
- When the "Focus First Child" button is clicked, the
ParentComponentshould call a method on the first renderedChildComponentinstance.
-
Child Component (
ChildComponent):- It should accept a single string prop,
text. - It should have a simple template that displays the
textprop. - It must expose a public method,
highlight, which can be called from the parent. This method should visually indicate that it has been called (e.g., by changing the background color of its root element).
- It should accept a single string prop,
-
Template Refs:
- You will need to use the
refattribute in theParentComponent's template to obtain a reference to theChildComponentinstances. - Consider how to handle multiple child components and get a reference specifically to the first one.
- You will need to use the
Expected Behavior:
When the "Focus First Child" button is clicked, the highlight method of the first ChildComponent in the list should be executed, visually changing its appearance. If there are no child components rendered, clicking the button should do nothing.
Edge Cases:
- No Child Components: The parent component should gracefully handle cases where the data array is empty, meaning no child components are rendered. The "Focus First Child" button should be present, but clicking it should not result in an error.
- Dynamic List: While not strictly required for this challenge, consider how your solution would adapt if the list of child components were dynamically added or removed. (Your solution should work for a static list as per the requirements).
Examples
Example 1:
ParentComponent.vue (Simplified):
<template>
<div>
<button @click="focusFirstChild">Focus First Child</button>
<ChildComponent v-for="(item, index) in items" :key="index" :text="item" :ref="el => childRefs[index] = el" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import ChildComponent from './ChildComponent.vue';
const props = defineProps<{ items: string[] }>();
const childRefs = reactive<any[]>([]); // Array to hold refs for multiple children
const focusFirstChild = () => {
if (childRefs.length > 0 && childRefs[0]) {
childRefs[0].highlight();
}
};
</script>
ChildComponent.vue (Simplified):
<template>
<div class="child">
{{ text }}
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps<{ text: string }>();
const highlight = () => {
// Implementation to visually highlight the element
console.log(`Highlighting: ${props.text}`);
// In a real scenario, you'd manipulate the DOM or add a class
const el = document.querySelector('.child') as HTMLElement; // This is a simplified way for demo
if (el) {
el.style.backgroundColor = 'yellow';
}
};
defineExpose({ highlight });
</script>
<style scoped>
.child {
border: 1px solid black;
padding: 10px;
margin-bottom: 5px;
}
</style>
Input:
ParentComponent is rendered with items = ['Apple', 'Banana', 'Cherry'].
Expected Output (Visual):
Initially, the three ChildComponent instances are displayed. When the "Focus First Child" button is clicked, the ChildComponent displaying "Apple" should have its background color changed to yellow.
Explanation:
The ParentComponent iterates through its items prop, rendering a ChildComponent for each. A ref is dynamically assigned to each ChildComponent instance using a function el => childRefs[index] = el. When the button is clicked, focusFirstChild accesses the first element in the childRefs array (childRefs[0]) and calls its highlight method.
Example 2:
Input:
ParentComponent is rendered with an empty items prop (items = []).
Expected Output (Visual/Behavior):
The "Focus First Child" button is displayed. Clicking the button has no effect, and no errors occur.
Explanation:
The childRefs array will be empty. The if (childRefs.length > 0 && childRefs[0]) condition in focusFirstChild prevents any attempt to access childRefs[0], thus avoiding errors.
Constraints
- Vue.js version: 3.x
- Language: TypeScript
- The
highlightmethod inChildComponentmust be explicitly exposed usingdefineExpose. - The solution should be performant, avoiding unnecessary re-renders or DOM queries where possible.
- Use Composition API for both components.
Notes
- When using
v-forwith template refs, you'll often end up with an array of references. Think about how to manage this array and access specific elements within it. - The
refattribute can accept a string (for Options API) or a function (for Composition API) or arefobject (Composition API, typically for DOM elements). For accessing child component instances in Composition API withv-for, a function ref is a common and effective pattern. - Remember to properly type your refs where appropriate. For this challenge,
any[]is acceptable forchildRefsto simplify the focus on template ref usage, but in a production scenario, you'd aim for more specific typing. - The
highlightmethod's visual indication can be as simple as changing a CSS style directly for this exercise.