Mastering Scoped Slots in Vue.js
Scoped slots are a powerful feature in Vue.js that allow child components to pass data back to their parent components, enabling more flexible and reusable component design. This challenge will test your understanding of how to implement and utilize scoped slots to create dynamic and data-rich UI elements.
Problem Description
Your task is to create a reusable ItemList component in Vue.js using TypeScript that displays a list of items. The parent component should be able to customize how each individual item is rendered, including displaying specific data properties of the item and potentially adding interactive elements.
Key Requirements:
-
ItemListComponent:- Accepts an array of
itemsas a prop. Each item in the array will be an object with at leastid(number) andname(string) properties. - Must use a scoped slot to render each individual item.
- The scoped slot should expose the current
itembeing rendered to the parent. - The component should iterate over the
itemsprop and render each one using the provided scoped slot.
- Accepts an array of
-
Parent Component (Usage Example):
- Will provide an array of items to the
ItemListcomponent. - Will utilize the scoped slot to define how each item is displayed.
- The parent's template should demonstrate passing data back from the slot (e.g., handling a click event on an item).
- Will provide an array of items to the
Expected Behavior:
When the ItemList component is used, each item in the provided items array should be rendered according to the template defined in the parent component's slot. The parent should have access to the individual item data within the slot scope.
Edge Cases to Consider:
- An empty
itemsarray should result in nothing being rendered by theItemListcomponent (no errors).
Examples
Example 1:
ItemList Component (Simplified for clarity):
<template>
<div>
<div v-for="item in items" :key="item.id">
<slot name="item" :item="item"></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
interface Item {
id: number;
name: string;
}
export default defineComponent({
props: {
items: {
type: Array as () => Item[],
required: true,
},
},
});
</script>
Parent Component Usage:
<template>
<div>
<h2>My Items</h2>
<ItemList :items="myItems">
<template #item="{ item }">
<div @click="handleItemClick(item)">
{{ item.name }} (ID: {{ item.id }})
</div>
</template>
</ItemList>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ItemList from './ItemList.vue';
interface Item {
id: number;
name: string;
}
export default defineComponent({
components: {
ItemList,
},
data() {
return {
myItems: [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' },
] as Item[],
};
},
methods: {
handleItemClick(item: Item) {
alert(`Clicked on: ${item.name}`);
},
},
});
</script>
Output (Rendered HTML):
<div>
<h2>My Items</h2>
<div>
<div>
<div onclick="alert('Clicked on: Apple')">
Apple (ID: 1)
</div>
</div>
<div>
<div onclick="alert('Clicked on: Banana')">
Banana (ID: 2)
</div>
</div>
<div>
<div onclick="alert('Clicked on: Cherry')">
Cherry (ID: 3)
</div>
</div>
</div>
</div>
Explanation: The ItemList component iterates through myItems. For each item, it renders the content provided by the #item slot in the parent. The item data is made available within the slot scope, allowing the parent to display item.name and item.id, and to attach an onclick handler that receives the item object.
Example 2: Handling an Empty List
ItemList Component: (Same as Example 1)
Parent Component Usage:
<template>
<div>
<h2>Empty List Example</h2>
<ItemList :items="emptyItems">
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</ItemList>
<p v-if="emptyItems.length === 0">No items to display.</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ItemList from './ItemList.vue';
interface Item {
id: number;
name: string;
}
export default defineComponent({
components: {
ItemList,
},
data() {
return {
emptyItems: [] as Item[],
};
},
});
</script>
Output (Rendered HTML):
<div>
<h2>Empty List Example</h2>
<p>No items to display.</p>
</div>
Explanation: When items is an empty array, the v-for loop inside ItemList does not execute, and no slot content is rendered. The parent component can conditionally render a message if there are no items.
Constraints
- The
ItemListcomponent should only use a single scoped slot nameditem. - The
itemsprop will always be an array of objects. Each object will have at leastid(number) andname(string) properties. - The solution should be implemented using Vue 3 and TypeScript.
- Focus on the concept of scoped slots; complex styling or advanced Vue features are not required.
Notes
- Remember to define the
Iteminterface for type safety. - Think about how the parent component can receive data from the slot. This is the core of scoped slots.
- Consider how you would provide additional data to the slot if needed, beyond just the
itemitself. (This is a hint for further exploration, not a strict requirement for this challenge).