VTC 2 Preview Digital
VTC 2 Preview Digital
COLLECTION
Michael Thiessen
Second Edition
Vue Tips Collection
1. Forgotten Features
1. Private properties with script setup. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2. h and Render Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3. Directives in Render Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4. Custom Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5. Deep Linking with Vue Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
6. Destructuring in a v-for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
7. Global Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
8. Next Tick: Waiting for the DOM to Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
9. A simpler way to pass lots of props . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
10. Restrict a prop to a list of types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
11. toRef default value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
12. Special CSS pseudo-selectors in Vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20
13. How to watch anything in your component . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
14. Watching Nested Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
15. Watching Arrays and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
16. Vue to Web Component in 3 Easy Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
II
Logic Lore
31. A bunch of composable mini tips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
32. Configuring Composables with the Options Object . . . . . . . . . . . . . . . . . . . . . . . . 53
33. Example of a Composable Using the Options Object Pattern . . . . . . . . . . . . . . . . . .56
34. The Right Number of Options for Composables . . . . . . . . . . . . . . . . . . . . . . . . . .59
35. Async Without Await . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
36. Dynamic Returns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .64
37. Flexible Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .66
38. Ref vs. Reactive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .68
39. Too Many Props/Options is a Smell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .70
40. Nesting Reactive Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
41. When ref and reactive work the same . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
42. Reassigning and Reactivity. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
43. Template Refs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
44. Avoid Ref Soup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
45. Composable Return Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .83
46. Global Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
47. Inline Composables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
48. From Options to Composition — The Easy Way . . . . . . . . . . . . . . . . . . . . . . . . . . .89
49. Shallow Refs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
50. Structuring Composables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93
51. How to Watch Props for Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .94
52. Wrapping Non-Reactive Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
53. Writable Computed Refs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .98
Nuxt Nuggets
54. Flatten Nuxt Content Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
55. Auto-imports in Nuxt 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
56. Nuxt Content Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
57. Handle Client-side Errors in Nuxt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
58. Prose Components in Nuxt 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
59. Reactive Routes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
60. SSR Safe Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
61. Nuxt’s Powerful Built-In Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
62. Using useHead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
63. Using useRoute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Template Tidbits
86. How to get rid of extra template tags. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
87. Another Use for the Template Tag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
88. Debugging Templates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
89. Detect mouse hover in your component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
90. Dynamic Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
91. Dynamic Slot Names. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
92. Looping Over a Range in Vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
93. Multiple v-models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
94. Nested Ref Properties in Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
95. Reactive SVG components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
96. Static and dynamic classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
97. Teleportation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
98. Computed Props in Your Template: v-memo . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
99. v-once . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
100. When should you use v-if? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
IV
All the Others
101. Aria roles you didn’t know you needed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
102. A better way to handle errors (and warnings) . . . . . . . . . . . . . . . . . . . . . . . . . . 182
103. Components are Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
104. Destructuring and Reactivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
105. Get rid of the double curly braces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
106. Force Vue to Re-Render Correctly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
107. Forcing a Component to Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
108. Hybrid API: Composition API + Options API . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
109. What are all these loops for? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
110. Lightweight State Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
111. Multi-file single-file components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
112. Performance Tracing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
113. Refresh a Page in Vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
114. Stacking contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
115. Start with the Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
116. Using two script blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
117. UI states to get right . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
118. Check Vue’s Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
You can limit what properties are available when a component is accessed by $ref :
export default {
expose: ['makeItPublic'],
data() {
return {
privateData: 'Keep me a secret!',
};
},
computed: {
makeItPublic() {
return this.privateData.toUpperCase();
},
},
};
With only makeItPublic exposed, you can't access the privateData property through a
$ref anymore:
If you’re using <script setup> , everything is locked down by default. If you want to expose
a value you have to do so explicitly:
Here defineExpose is a compiler macro, not an actual function, so we don't have to import
anything.
3 FORGOTTEN FEATURES
2.
h and Render Functions
When using the render function instead of templates, you'll be using the h function a lot:
<script setup>
import { h } from 'vue';
const render = () => h('div', {}, 'Hello Wurld');
</script>
With the Options API the render function works exactly the same, we just have to define it
slightly differently:
<script>
import { h } from 'vue';
export default {
render() {
return h('div', {}, 'Hello Wurld');
}
}
</script>
It creates a VNode (virtual node), an object that Vue uses internally to track updates and what
it should be rendering.
The first argument is either an HTML element name or a component (which can be async if
you want):
<script setup>
import { h } from 'vue';
import MyComponent from './MyComponent.vue';
<script setup>
import { h } from 'vue';
import MyComponent from './MyComponent.vue';
The third argument is either a string for a text node, an array of children VNodes, or an object
for defining slots:
<script setup>
import { h } from 'vue';
import MyComponent from './MyComponent.vue';
These render functions are essentially what is happening "under the hood" when Vue compiles
your single file components to be run in the browser.
But by writing out the render function yourself, you are no longer constrained by what can be
done in a template. You have the full power of Javascript at your fingertips
This is just scratching the surface on what render functions and h can do. Read more about
them online.
5 FORGOTTEN FEATURES
3.
Directives in Render Functions
Vue comes with some methods to help you use custom directives on your VNodes:
<script setup>
import { resolveDirective, withDirectives, h } from 'vue';
Render functions are defined slightly differently when using the Options API:
export default {
render() {
// Find the already registered directive by name
const focusDirective = resolveDirective('focus');
7 FORGOTTEN FEATURES
4.
Custom Directives
In script setup you can define a custom directive just by giving it a camelCase name that
starts with v :
<script setup>
const vRedBackground = {
mounted: (el) => el.style.background = 'red',
}
</script>
<template>
<input v-red-background />
</template>
export default {
setup() {
// ...
},
directives: {
redBackground: {
mounted: (el) => el.style.background = 'red',
},
},
}
And since a very common use case is to have the same logic for the mounted and updated
hooks, we can supply a function instead of an object that will be run for both of them:
<script setup>
const vRedBackground = (el) => el.style.background = 'red';
</script>
<template>
<input v-red-background />
</template>
9 FORGOTTEN FEATURES
5.
Deep Linking with Vue Router
You can store (a bit of) state in the URL, allowing you to jump right into a specific state on the
page.
For example, you can load a page with a date range filter already selected:
someurl.com/edit?date-range=last-week
This is great for the parts of your app where users may share lots of links, for a server-rendered
app, or for communicating more information between two separate apps than a regular link
provides typically.
You can store filters, search values, whether a modal is open or closed, or where in a list we've
scrolled to — perfect for infinite pagination.
Grabbing the query using vue-router works like this (this will work on most Vue frame-
works like Nuxt and Vuepress too):
// Composition API
const dateRange = useRoute().query.dateRange;
// Options API
const dateRange = this.$route.query.dateRange;
<RouterLink :to="{
query: {
dateRange: newDateRange
}
}">
<li
v-for="{ name, id } in users"
:key="id"
>
{{ name }}
</li>
It’s more widely known that you can grab the index out of the v-for by using a tuple like this:
11 FORGOTTEN FEATURES
It's also possible to combine these two methods, grabbing the key as well as the index of the
property:
When you register a component globally, you can use it in any template without importing it a
second time:
// Vue 3
import { createApp } from 'vue';
import GlobalComponent from './GlobalComponent.vue';
// Vue 2
import Vue from 'vue';
import GlobalComponent from './GlobalComponent.vue';
Vue.component('GlobalComponent', GlobalComponent);
Now you can use GlobalComponent in your templates without any extra work!
Of course, globally registered components have the same pros and cons as global variables. So
use this sparingly.
13 FORGOTTEN FEATURES
8.
Next Tick: Waiting for the DOM to Update
Vue gives us a super handy way for us to wait for the DOM to finish updating:
await this.$nextTick();
A tick is a single render cycle. First, vue listens for any reactivity changes, then performs sev-
eral updates to the DOM in one batch. Then the next tick begins.
If you update something in your app that will change what is rendered, you have to wait until
the next tick before that change shows up.
<template>
<User
:name="user.name"
:profile="user.profile"
:twitter="user.twitter"
:location="user.location"
:framework="user.framework === 'Vue' ? 'Number one' : 'Number two'"
/>
</template>
You can take a whole object and have all of its properties automatically bound to the compo-
nent as props:
<template>
<User v-bind="user"/>
</template>
<script setup>
import User from './User.vue';
const user = {
name: 'Anakin',
profile: 'ani-profile.jpg',
twitter: '@TatooineJedi',
location: 'Undisclosed',
framework: 'Vue',
};
</script>
15 FORGOTTEN FEATURES
This also works with v-on if you have a lot of event handlers:
<template>
<User v-on="userEventHandlers"/>
</template>
<script setup>
import User from './User.vue';
const userEventHandlers = {
updateName(newName) {
// ...
},
deleteUser() {
// ...
},
addFriend(friend) {
// ...
}
};
</script>
Here, the name of each method needs to match the name of the event. eg. updateName is
called to handle the update-name event.
With the Composition API we get fantastic TypeScript support, so this is quite straightforward:
defineProps<{
src: string;
style: 'square' | 'rounded';
}>();
Doing this in the Options API is more complicated, and not as powerful as TypeScript.
Using the validator option in a prop definition you can restrict a prop to a specific set of
values:
export default {
name: 'Image',
props: {
src: {
type: String,
},
style: {
type: String,
validator: s => ['square', 'rounded'].includes(s)
}
}
};
17 FORGOTTEN FEATURES
This validator function takes in a prop and returns either true or false — if the prop is
valid or not.
I often restrict props like this when I need more options than a boolean will allow but still
want to restrict what can be set.
Button types or alert types (info, success, danger, warning) are some of the most common uses
— at least in what I work on. Colours, too, are a really great use case for this.
You’ve been using toRef for a while, but did you know you can also supply a default value?
19 FORGOTTEN FEATURES
12.
Special CSS pseudo-selectors in Vue
If you want some styles to apply specifically to slot content, you can do that with the
:slotted pseudo-selector:
<style scoped>
/* Add margin to <p> tags within the slot */
:slotted(p) {
margin: 15px 5px;
}
</style>
You can also use :global to have styles apply to global scope, even within the
<style scoped> block:
<style scoped>
:global(body) {
margin: 0;
padding: 0;
font-family: sans-serif;
}
</style>
Of course, if you have lots of global styles you want to add, it’s probably easier to just add a
second <style> block:
<style>
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
</style>
21 FORGOTTEN FEATURES
13.
How to watch anything in your component
It took me a very long time to realize this, but anything in your component that is reactive can
be watched. This includes computed props as well:
Maybe it’s just me, but for some reason this wasn’t all that intuitive at first for me.
export default {
computed: {
someComputedProperty() {
// Update the computed prop
},
},
watch: {
someComputedProperty() {
// Do something when the computed prop is updated
}
}
};
• computed props
• props
• nested values
You may not have known this, but you can easily watch nested values directly when using the
Options API, just by using quotes:
watch: {
'$route.query.id'() {
// ...
}
}
watch(
() => value.that.is.really.nested,
() => {
// ...
}
);
23 FORGOTTEN FEATURES
15.
Watching Arrays and Objects
The trickiest part of using a watcher is that sometimes it doesn’t seem to trigger correctly.
Usually, this is because you’re trying to watch an Array or an Object but didn’t set deep
to true :
watch(
colours,
() => {
console.log('The list of colours has changed!');
},
{
deep: true,
}
);
export default {
name: 'ColourChange',
props: {
colours: {
type: Array,
required: true,
},
},
watch: {
// Use the object syntax instead of just a method
colours: {
// This will let Vue know to look inside the array
deep: true,
25 FORGOTTEN FEATURES
16.
Vue to Web Component in 3 Easy Steps
First, create the custom element from a Vue component using defineCustomElement:
customElements.define('my-vue-component', customElement);
<html>
<head></head>
<body>
<my-vue-component></my-vue-component>
</body>
</html>
Now you’ve got a custom web component that doesn’t need a framework and can run natively
in the browser! Check out the docs for more details on how this works.
There are six different levels of reusability that you can use in your components.
As you start creating more abstractions with Vue, you may need to begin nesting your slots:
This works similarly to how you would catch an error and then re-throw it using a try...
catch block:
try {
// Catch the error
reallyRiskyOperation();
} (e) {
// Then re-throw as something else for the next
// layer to catch and handle
throw new ThisDidntWorkError('Heh, sorry');
}
<template>
<div>
<!-- Nothing to see here, just a regular slot -->
<slot />
</div>
</template>
But if we don’t want to render it in this component and instead pass it down again, we render
the slot content inside of another slot:
<template>
<Child>
<!-- This is the same as the previous code example,
but instead of a 'div' we render into a component. -->
<slot />
</Child>
</template>
I decided to see if I could make a v-for component using only the template. Along the way,
I discovered how to use slots recursively, too.
If you wanted to do this with scoped slots — and why wouldn’t you?! — it just takes a few
tweaks:
<template>
<div>
<!-- Pass the item into the slot to be rendered -->
<slot v-bind:item="list[0]">
<!-- Default -->
{{ list[0] }}
</slot>
<template>
<div>
<!-- Regular list -->
<v-for :list="list" />
For a more detailed explanation of this example and nested slots, check out my blog post on it.
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 defaults , 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:
If we only apply one slot to the component, only that slot will show up in our $slots object:
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 lay-
out and make things look weird.
For example, when we’re adding default styles, we’re adding a div around a slot:
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>
I wrote more tips on slots in this article: Tips to Supercharge Your Slots (Named, Scoped, and
Dynamic)
Let’s say we’re building an If component and we want to use it in two main ways — with a
default slot, or with two named slots.
<If :val="putConditionHere">
Renders only if it's true
</If>
<If :val="putConditionHere">
<template #true>
Renders only if it's true
</template>
<template #false>
Renders only if the condition is false
</template>
</If>
This is how we’d have to arrange the slots in order to make this work:
<template>
<slot v-if="val" />
<template v-if="!$slots.default">
<slot v-if="val" name="true" />
<slot v-if="!val" name="false" />
</template>
</template>
We need to do this, otherwise we’ll render the default and true slots whenever our
condition is true, which isn’t what we want! We want these to be mutually exclusive — you can
either use the default slot or the named true slot.
Just the default slot to access the true branch, or using two named slots to access both the true
and false branches.
If you have multiple levels of nested slots, it’s possible to have defaults at each level:
The slot content provided at the highest point in the hierarchy will override everything below it.
If we render Parent , it will always display We’re in the Parent . But if we render just
the Child component, we get We’re in the Child !. And if the component rendering the
Parent component provides slot content, that will take precedence over everything:
You can provide fallback content for a slot, in case no content is provided:
This content can be anything, even a whole complex component that provides default
behaviour:
The solution to these two problems is the same, but we’ll get there in a second.
Many components you create are contentless components. They provide a container, and
you have to supply the content. Think of a button, a menu, an accordion, or a card component:
You can often pass this content in as a regular String . But many times, you want to pass in a
whole chunk of HTML, maybe even a component or two.
*again, yes, you could do this, but you’ll definitely regret it.
Props also require that you plan for all future use cases of the component. If your Button
component only allows two values for type , you can’t just use a third without modifying the
Button :
...slots!
Slots allow you to pass in whatever markup and components you want, and they also are
relatively open-ended, giving you lots of flexibility. This is why in many cases, slots are simply
better than props.
Named slots also have a shorthand syntax, one that’s much nicer to look at.
<DataTable>
<template v-slot:header>
<TableHeader />
</template>
</DataTable>
<DataTable>
<template #header>
<TableHeader />
</template>
</DataTable>
Not a huge difference, but a little cleaner for sure. I think the # character is easier to pick out
than v-slot when reading code.
It’s possible to use transitions with slot content, but there’s one key to making them work
smoothly:
<template>
<SlotWithTransition>
<div v-if="isThisTrue" key="true">
This is true.
</div>
<div v-else key="false">
This is false.
</div>
</SlotWithTransition>
</template>
Because I’m not re-writing this code all over the place, updating it becomes much easier, and
I can make sure that every OverflowMenu looks and works exactly the same — because they
are the same!
It almost seems like it’s not worth making a reusable component out of this because it’s only a
few lines. Can’t we just add the icon every time we want to use a Menu like this?
But this OverflowMenu will be used dozens of times, and now if we want to update the icon
or its behaviour, we can do it very quickly. And using it is much simpler too!
<template>
<OverflowMenu
:menu-items="items"
@click="handleMenuClick"
/>
</template>
Our Child component only accepts one slot, but the Parent component accepts two.
Here, the Parent component switches between which slot it uses based on the value of left .
We can also use default slot content, if one or both of the Parent slots have no content:
One kind of prop, a template prop, can be directly converted into slots without very much
work.
The text prop here is a template prop, because it is only ever used in the template:
<template>
<button @click="$emit('click')">
{{ text }}
</button>
</template>
<script setup>
const props = defineProps({
text: {
type: String,
required: true,
},
});
defineEmits(['click']);
</script>
It doesn’t get used in any calculations or passed as a prop anywhere. Instead, it just gets direct-
ly interpolated and rendered to the page.
<template>
<button @click="$emit('click')">
<slot />
</button>
</template>
<script setup>
defineEmits(['click']);
</script>
This sort of cleans up the code, but more importantly, it allows us to be more flexible with how
the component can be used.
<Button @click="handleClick">
Click on <strong>this</strong> button
</Button>
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: