Scoped Slots: The Power of Delegating UI Logic

Scoped Slots: The Power of Delegating UI Logic

Vue.js has introduced a new feature called scoped slots that allows us to delegate UI logic from one component to another. This powerful tool enables us to create reusable components that can be used to render complex data structures with ease.

What are Scoped Slots?

In Vue, a slot is a way to insert content into a template at specific points. A scoped slot is a type of slot that allows you to pass props from one component to another. This means that the consuming component can use these props to customize the rendering of the child component.

Let's take a look at an example:

function MyComponent(slots) {
 const greetingMessage = 'hello'
 return `<div>${
 // call the slot function with props!
 slots.default({ text: greetingMessage, count: 1 })
 }</div>`
}

<template><MyComponent v-slot="{ text, count }">
 {{ text }} {{ count }}
</MyComponent></template>

As you can see, we're using the v-slot directive to pass props from the consuming component to our MyComponent. We can then use these props within our MyComponent template.

Named Scoped Slots

In addition to default scoped slots, Vue also provides named scoped slots. Named scoped slots allow us to define multiple slots with different names and access them as props in our child component.

Let's look at an example:

<template>
 <MyComponent>
 <template #header="headerProps">
 {{ headerProps }}
 </template>

 <template #default="defaultProps">
 {{ defaultProps }}
 </template>

 <template #footer="footerProps">
 {{ footerProps }}
 </template>
 </MyComponent>
</template>

As you can see, we're using the # symbol to define three different slots: header, default, and footer. We then use these slots as props within our child component.

Passing Props to a Named Slot

When we pass props to a named slot, Vue automatically removes the name from the props. This means that if we have a slot with the name header and we pass an object like { message: 'hello' }, the resulting headerProps would be { message: 'hello' }.

Let's look at an example:

<slot name="header" message="hello"></slot>

As you can see, we're passing an object with a single property message. The resulting headerProps would be { message: 'hello' }.

Mixing Named Slots and Default Scoped Slots

When we mix named slots with default scoped slots, we need to use an explicit <template> tag for the default slot. This is because Vue can't determine which scope the props belong to.

Let's look at an example:

<template>
 <div>
 <slot :message="hello"></slot>
 <slot name="footer" />
 </div>
</template>

<!-- This template won't compile -->
<MyComponent v-slot="{ message }">
 <p>{{ message }}</p>
 <template #footer>
 <!-- message belongs to the default slot, and is not available here -->
 <p>{{ message }}</p>
 </template>
</MyComponent>

As you can see, we're trying to use the message prop within a named slot. However, this won't compile because Vue can't determine which scope the props belong to.

Fancy List Example

Let's take a look at an example where we create a <FancyList> component that renders a list of items. We want each item to have different styling and leave it up to the consuming component to decide how they're styled.

Here's our FancyList component:

<template><FancyList :api-url="url" :per-page="10">
 <template #item="{ body, username, likes }">
 <div class="item">
 <p>{{ body }}</p>
 <p>by {{ username }} | {{ likes }} likes</p>
 </div>
 </template>
</FancyList></template>

As you can see, we're using a named slot #item to render each item. We then use this slot in our consuming component:

<template><ul>
 <li v-for="item in items">
 <slot name="item" v-bind="item"></slot>
 </li>
</ul></template></template>

As you can see, we're using the v-slot directive to pass each item as a prop to our named slot. We then use this prop within our consuming component.

Renderless Components

In addition to scoped slots, Vue also provides renderless components. A renderless component is a component that doesn't render any UI itself but only provides data or functionality to the consuming component.

Let's take a look at an example:

<template><MyComponent :data="items">
 <!-- rendering will happen here -->
</MyComponent></template>

As you can see, we're using our MyComponent as a renderless component. We then use this component in our consuming component:

<template><ul>
 <li v-for="item in items">
 {{ item.name }}
 </li>
</ul></template>

As you can see, we're using the data provided by our MyComponent to render a list of items., scoped slots are a powerful tool that allows us to delegate UI logic from one component to another. By passing props and using named slots, we can create reusable components that can be used to render complex data structures with ease.