🔥 (#127) Understanding Scoped Slots
Hey there!
I'm back from my summer vacation and getting back to things — namely, Clean Components.
This week it's pulling together the final pieces so I can launch the pre-release version (hopefully soon).
So I'm editing content, fixing the show-stopping bugs I've ignored until now, and recording videos.
I am extremely excited for you to finally see my vision come to life!
I think you'll love it.
— Michael
Vue Tips Collection
Maybe you just want to stay on top of the latest features, remind yourself of interesting things Vue can do, and get daily inspiration.
Vue Tips Collection is a beautiful book of 115 awesome tips, as well as a daily email to get your creative juices flowing.
🔥 Understanding scoped slots
Here's the best way to think about scoped slots:
Scoped slots are like functions that are passed to a child component that returns HTML.
Once the template is compiled, they are functions that return HTML (technically vnodes
) that the parent passes to the child.
Here's a simple list that uses a scoped slot to customize how we render each item:
<!-- Parent.vue --> <template> <ScopedSlotList :items="items"> <template v-slot="{ item }"> <!-- Make it bold, just for fun --> <strong>{{ item }}</strong> </template> </ScopedSlotList> </template>
<!-- ScopedSlotList.vue --> <template> <ul> <li v-for="item in items" :key="item" > <slot :item="item" /> </li> </ul> </template>
We can rewrite this example to use a function instead of a scoped slot:
<!-- Parent.vue --> <template> <ScopedSlotList :items="items" :scoped-slot="(item) => `<strong>${item}</strong>`" > </template>
<!-- ScopedSlotList.vue --> <template> <ul> <li v-for="item in items" :key="item" v-html="scopedSlot(item)" /> </ul> </template>
🔥 How I deal with dynamic classes
A pattern I use constantly is triggering classes with boolean
flags:
<template> <div :class="disabled && 'disabled-component'"> Sometimes this component is disabled. Other times it isn't. </div> </template> <style scoped> /* Some styles */ .disabled-component { background-color: gray; color: darkgray; cursor: not-allowed; } </style>
Either the trigger is a prop I use directly, or a computed prop that tests for a specific condition:
disabled() { return this.isDisabled || this.isLoading; }
If I just need one class on an element, I use the logical AND to trigger it:
<div :class="disabled && 'disabled-component'"></div>
Sometimes it's a decision between two classes, so I'll use a ternary:
<div :class="disabled ? 'disabled-component' : 'not-yet-disabled'" />
I don't often use more than two classes like this, but that's where an Object
or Array
comes in handy:
<div :class="{ primary: isPrimary, secondary: isSecondary, tertiary: isTertiary, }" /> <div :class="[ isPrimary && 'primary', isSecondary && 'secondary', isTertiary && 'tertiary', ]" />
Of course, when it gets complex enough it's better to just have a computed prop that returns a string of class names (or returns an Object
or Array
):
<div :class="computedClasses" />
🔥 Directly accessing parent components (and why)
Props down, events up. That's how your components should communicate — most of the time.
But in rare cases, that just doesn't work.
If you need direct access to the parent component, you should just use provide
/inject
to pass down the relevant value or method:
import { provide } from 'vue'; const someMethodInTheParent = () => {}; provide('method', someMethodInTheParent)
Then, inject it into the child component:
import { inject } from 'vue'; const method = inject('method'); method();
In Vue 2, you can also use the instance property $parent
:
// Tight coupling like this is usually a bad idea this.$parent.methodOnParentComponent();
This is simpler, but leads to higher coupling and will more easily break your application if you ever refactor.
You can also get direct access to the application root, the very top-most component in the tree, by using $root
. Vue 2 also has $children
, but these were taken out for Vue 3 (please don't use this one).
When would these be useful?
There are a few different scenarios I can think of. Usually, when you want to abstract some behaviour and have it work "magically" behind the scenes.
You don't want to use props and events to connect up a component in those cases. Instead, you use provide
/inject
, $parent
, or $root
, to automatically connect the components and make things happen.
(This is similar to the Compound Component pattern)
But it's hard to come up with an example where this is the best solution. Using provide
/inject
is almost always the better choice.
📜 Data Fetching Basics in Nuxt 3
Nuxt offers a set of powerful built-in tools for handling data fetching.
It provides composable functions that make it easy to fetch data and automatically handle server-side rendering, client-side hydration, and error handling. This enables you to write clean and efficient code, ensuring an optimal user experience.
In this article we'll examine the different methods Nuxt gives us for data fetching.
Check it out here: Data Fetching Basics in Nuxt 3
💬 90%
"The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time." — Tom Cargill
🧠 Spaced-repetition: Conditionally Rendering Slots
The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨🔬
Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.
First, I'll show you how, then we'll get into why you'd want to hide slots.
Every Vue component has a special $slots
object with all of your slots in it. The default slot has the key default
, and any named slots use their name as the key:
const $slots = { default: <default slot>, icon: <icon slot>, button: <button slot>, };
But this $slots
object only has the slots that are applied to the component, not every slot that is defined.
Take this component that defines several slots, including a couple named ones:
<!-- Slots.vue --> <template> <div> <h2>Here are some slots</h2> <slot /> <slot name="second" /> <slot name="third" /> </div> </template>
If we only apply one slot to the component, only that slot will show up in our $slots
object:
<template> <Slots> <template #second> This will be applied to the second slot. </template> </Slots> </template>
$slots = { second: <vnode> }
We can use this in our components to detect which slots have been applied to the component, for example, by hiding the wrapper element for the slot:
<template> <div> <h2>A wrapped slot</h2> <div v-if="$slots.default" class="styles"> <slot /> </div> </div> </template>
Now the wrapper div
that applies the styling will only be rendered if we actually fill that slot with something.
If we don't use the v-if
, we will have an empty and unnecessary div
if we don't have a slot. Depending on what styling that div
has, this could mess up our layout and make things look weird.
So why do we want to be able to conditionally render slots?
There are three main reasons to use a conditional slot:
- When using wrapper `div's to add default styles
- The slot is empty
- If we're combining default content with nested slots
For example, when we're adding default styles, we're adding a div
around a slot:
<template> <div> <h2>This is a pretty great component, amirite?</h2> <div class="default-styling"> <slot > </div> <button @click="$emit('click')">Click me!</button> </div> </template>
However, if no content is applied to that slot by the parent component, we'll end up with an empty div
rendered to the page:
<div> <h2>This is a pretty great component, amirite?</h2> <div class="default-styling"> <!-- No content in the slot, but this div is still rendered. Oops. --> </div> <button @click="$emit('click')">Click me!</button> </div>
Adding that v-if
on the wrapping div
solves the problem though. No content applied to the slot? No problem:
<div> <h2>This is a pretty great component, amirite?</h2> <button @click="$emit('click')">Click me!</button> </div>
Here's a Codesandbox with a working demo if you want to take a look: https://codesandbox.io/s/reactive-slots-bth28?file=/src/components/HasSlot.vue
I wrote more tips on slots in this article: Tips to Supercharge Your Slots (Named, Scoped, and Dynamic)
p.s. I also have four courses: Vue Tips Collection, Mastering Nuxt 3, Reusable Components and Clean Components
评论
发表评论