Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
252 views56 pages

VTC 2 Preview Digital

The document provides a collection of tips for using Vue and Nuxt including forgotten features, slots and reusability, logic patterns, and Nuxt-specific patterns. It covers topics like script setup, render functions, composables, slots, routing, and more.

Uploaded by

Pham Nhat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
252 views56 pages

VTC 2 Preview Digital

The document provides a collection of tips for using Vue and Nuxt including forgotten features, slots and reusability, logic patterns, and Nuxt-specific patterns. It covers topics like script setup, render functions, composables, slots, routing, and more.

Uploaded by

Pham Nhat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

VUE TIPS

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

Slots and Reusability


17. 6 Levels of Reusability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
18. Nesting slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
19. Recursive slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
20. Conditionally Rendering Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
21. Using default and named slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .36
22. Default Content with Nested Slots. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .38
23. Default Content with Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .39
24. The limitations of props. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40
25. Shorthand for named slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
26. Slot Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .43
27. Reusable Components Aren’t What You Think . . . . . . . . . . . . . . . . . . . . . . . . . . .44
28. Splitting Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .46
29. Improve reusability by converting template props into slots . . . . . . . . . . . . . . . . . . 47
30. Understanding scoped slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49

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

VUE TIPS COLLECTION III


Powerful Patterns
64. The Hidden Component Pattern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
65. When to use the Hidden Component Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
66. Async Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
67. Component Variations with the Base Component Pattern . . . . . . . . . . . . . . . . . . . 123
68. Calling a Method from Outside of the Component. . . . . . . . . . . . . . . . . . . . . . . . 125
69. Component Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
70. Compound Components (Quickly) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
71. Reusability Fundamentals: The Configuration Pattern. . . . . . . . . . . . . . . . . . . . . . 133
72. Creating Magic with Context-Aware Components . . . . . . . . . . . . . . . . . . . . . . . . 135
73. Controlled Props — or How to Override Internal State. . . . . . . . . . . . . . . . . . . . . . 137
74. Decouple Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
75. Directly accessing parent components (and why) . . . . . . . . . . . . . . . . . . . . . . . . 140
76. Default Content and Extension Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
77. Extract Conditional Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
78. Lonely children. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
79. Make Testing Easy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
80. Master computed refs (and computed props). . . . . . . . . . . . . . . . . . . . . . . . . . . 149
81. Optimize for Humans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
82. How to make a variable created outside of Vue reactive . . . . . . . . . . . . . . . . . . . . 152
83. Reusing Code and Knowledge. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
84. Where do you put shared state?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
85. Smooth dragging (and other mouse movements) . . . . . . . . . . . . . . . . . . . . . . . . 156

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

VUE TIPS COLLECTION V


1 Forgotten
Features
All those things you forgot Vue could do, or didn't
know about in the first place
1.
Private properties with script setup

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:

this.$refs.component.privateData // Will always be undefined

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:

VUE TIPS COLLECTION 2


<script setup>
import { ref, computed } from 'vue';
const privateData = ref('Keep me a secret!');
const makeItPublic = computed(
() => privateData.value.toUpperCase()
);

// We don't need to import this because it's a compiler macro


defineExpose({
makeItPublic
});
</script>

Here defineExpose is a compiler macro, not an actual function, so we don't have to import
anything.

You can find more info on this on the official docs.

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';

const render = () => h(MyComponent, {}, []);


</script>

VUE TIPS COLLECTION 4


The second argument is a list of props, attributes, and event handlers:

<script setup>
import { h } from 'vue';
import MyComponent from './MyComponent.vue';

const render = () => h(MyComponent, {


class: 'text-blue-400',
title: 'This component is the greatest',
onClick() {
console.log('Clicked!');
},
}, []);
</script>

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';

const render = () => h(MyComponent, {}, [


'Simple text node',
h('span', {}, 'Text inside of a <span> element'),
]);
</script>

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';

// Find the already registered directive by name


const focusDirective = resolveDirective('focus');

// Wrap the button with the directive


const render = () => withDirectives(
h('button', {}, []),
// An array of directives to apply
[
[focusDirective]
]
);
</script>

Render functions are defined slightly differently when using the Options API:

import { resolveDirective, withDirectives, h } from 'vue';

export default {
render() {
// Find the already registered directive by name
const focusDirective = resolveDirective('focus');

VUE TIPS COLLECTION 6


// Wrap the button with the directive
return withDirectives(
h('button', {}, []),

// An array of directives to apply


[
[focusDirective]
]
);
}
}

You can find more info on using withDirectives on the docs.

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>

With the Options API:

export default {
setup() {
// ...
},
directives: {
redBackground: {
mounted: (el) => el.style.background = 'red',
},
},
}

VUE TIPS COLLECTION 8


Registering a directive globally:

const app = createApp({})

// make v-focus usable in all components


app.directive('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>

You can find more info on custom directives in the docs.

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;

To change it we use the RouterLink component and update the query :

<RouterLink :to="{
query: {
dateRange: newDateRange
}
}">

VUE TIPS COLLECTION 10


6.
Destructuring in a v-for

Did you know that you can destructure in a v-for ?

<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:

<li v-for="(movie, index) in [


'Lion King',
'Frozen',
'The Princess Bride'
]">
{{ index + 1 }} - {{ movie }}
</li>

When using an object you can also grab the key:

<li v-for="(value, key) in {


name: 'Lion King',
released: 2019,
director: 'Jon Favreau',
}">
{{ key }}: {{ value }}
</li>

11 FORGOTTEN FEATURES
It's also possible to combine these two methods, grabbing the key as well as the index of the
property:

<li v-for="(value, key, index) in {


name: 'Lion King',
released: 2019,
director: 'Jon Favreau',
}">
#{{ index + 1 }}. {{ key }}: {{ value }}
</li>

VUE TIPS COLLECTION 12


7.
Global Components

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';

const app = createApp({})


app.component('GlobalComponent', GlobalComponent);

In Vue 2 you can register global components like this:

// 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:

// Do something that will cause Vue to re-render


changeTheDOM();

// Wait for the re-render to finish


await nextTick();

// Now we can check out the freshly minted DOM


inspectTheDOM();

Or if you're using the Options API:

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.

VUE TIPS COLLECTION 14


9.
A simpler way to pass lots of props

Instead of passing in tons of props to a component individually:

<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.

VUE TIPS COLLECTION 16


10.
Restrict a prop to a list of types

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.

VUE TIPS COLLECTION 18


11.
toRef default value

You’ve been using toRef for a while, but did you know you can also supply a default value?

const bank = reactive({


Rand: 3400,
Egwene: 20,
Matrim: 230340,
Padan: -20340,
})

// toRef(object, property, default)


const myBankAccount = toRef(bank, 'Michael', 1000 * 1000);

Probably the easiest way to become a millionaire.

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:

VUE TIPS COLLECTION 20


<style scoped>
/* Add margin to <p> tags within the slot */
:slotted(p) {
margin: 15px 5px;
}
</style>

<style>
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
</style>

Check out the docs for more info.

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:

const first = ref('Michael');


const last = ref('Thiessen');
const fullName = computed(() => '${first.value} ${last.value}');

watchEffect(() => console.log(fullName.value));

Maybe it’s just me, but for some reason this wasn’t all that intuitive at first for me.

With the Options API it looks like this:

export default {
computed: {
someComputedProperty() {
// Update the computed prop
},
},
watch: {
someComputedProperty() {
// Do something when the computed prop is updated
}
}
};

You can watch:

• computed props
• props
• nested values

VUE TIPS COLLECTION 22


14.
Watching 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'() {
// ...
}
}

This is really useful for working with deeply nested objects!

We can also watch nested values using the Composition API:

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,
}
);

When using the Options API it would look like this:

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,

VUE TIPS COLLECTION 24


// We have to move our method to a handler field
handler()
console.log('The list of colours has changed!');
}
}
}
}

25 FORGOTTEN FEATURES
16.
Vue to Web Component in 3 Easy Steps

Here’s how you can create web components in Vue.

First, create the custom element from a Vue component using defineCustomElement:

import { defineCustomElement } from 'vue';


import MyVueComponent from './MyVueComponent.vue';

const customElement = defineCustomElement(MyVueComponent);

Second, register the custom element with the DOM:

customElements.define('my-vue-component', customElement);

Third, use the custom element in your HTML:

<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.

VUE TIPS COLLECTION 26


2 Slots and
Reusability
Slots are the most powerful feature in Vue. They let
us create more flexible and reusable components.
17.
6 Levels of Reusability

There are six different levels of reusability that you can use in your components.

Each level increases your ability to reuse code.

These levels are the focus of my course, Reusable Components.

Here are the six levels of reusability:

1. Templating — Reusing code by wrapping it up inside of a component

2. Configuration — Using configuration props to allow for varying behaviou

3. Adaptability — Allowing components to become future-proof

4. Inversion — Letting other components control the process

5. Extension — Using reusability throughout our component

6. Nesting — Creating powerful hierarchies of components

I cover this in more detail in this excerpt from the course.

VUE TIPS COLLECTION 28


18.
Nesting slots

As you start creating more abstractions with Vue, you may need to begin nesting your slots:

<!-- Parent.vue -->


<template>
<Child>
<!-- We take the content from the grand-parent slot
and render it inside the child's slot -->
<slot />
</Child>
</template>

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');
}

29 SLOTS AND REUSABILITY


Normally when using a slot we just render the content that’s provided to us:

<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>

VUE TIPS COLLECTION 30


19.
Recursive slots

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.

This is what the component looks like:

<!-- VFor.vue -->


<template>
<div>
<!-- Render the first item -->
{{ list[0] }}
<!-- If we have more items, continue!
But leave off the item we just rendered -->
<v-for
v-if="list.length > 1"
:list="list.slice(1)"
/>
</div>
</template>

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>

31 SLOTS AND REUSABILITY


<v-for
v-if="list.length > 1"
:list="list.slice(1)"
>
<!-- Recursively pass down scoped slot -->
<template v-slot="{ item }">
<slot v-bind:item="item" />
</template>
</v-for>
</div>
</template>

Here is how this component is used:

<template>
<div>
<!-- Regular list -->
<v-for :list="list" />

<!-- List with bolded items -->


<v-for :list="list">
<template v-slot="{ item }">
<strong>{{ item }}</strong>
</template>
</v-for>
</div>
</template>

For a more detailed explanation of this example and nested slots, check out my blog post on it.

VUE TIPS COLLECTION 32


20.
Conditionally Rendering Slots

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:

<!-- 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:

33 SLOTS AND REUSABILITY


<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 lay-
out 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:

1. When using wrapper `div’s to add default styles


2. The slot is empty
3. 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:

VUE TIPS COLLECTION 34


<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.

I wrote more tips on slots in this article: Tips to Supercharge Your Slots (Named, Scoped, and
Dynamic)

35 SLOTS AND REUSABILITY


21.
Using default and named slots

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.

Just the default slot:

<If :val="putConditionHere">
Renders only if it's true
</If>

Or using the named slots to access both branches:

<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>

VUE TIPS COLLECTION 36


You’ll notice we use a template to group the named slots. We’re also checking $slots to
see if we’ve put anything in the default slot or not.

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.

Now, we can use it in either way.

Just the default slot to access the true branch, or using two named slots to access both the true
and false branches.

37 SLOTS AND REUSABILITY


22.
Default Content with Nested Slots

If you have multiple levels of nested slots, it’s possible to have defaults at each level:

<!-- Parent.vue -->


<template>
<Child>
<slot>
We're in the Parent!
</slot>
</Child>
</template>

<!-- Child.vue -->


<template>
<div>
<slot>
We're in the Child!
</slot>
</div>
</template>

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:

<!-- Grandparent.vue -->


<template>
<Parent>
Haha this content rules them all!
</Parent>
</template>

VUE TIPS COLLECTION 38


23.
Default Content with Slots

You can provide fallback content for a slot, in case no content is provided:

<!-- Child.vue -->


<template>
<div>
<slot>
Hey! You forgot to put something in the slot!
</slot>
</div>
</template>

This content can be anything, even a whole complex component that provides default
behaviour:

<!-- Child.vue -->


<template>
<div>
<slot name="search">
<!-- Can be overridden with more advanced functionality -->
<BasicSearchFunctionality />
</slot>
</div>
</template>

39 SLOTS AND REUSABILITY


24.
The limitations of props

Props are helpful, but they have two glaring issues:

1. Impossible to pass markup*

2. Not that flexible


*not technically impossible, but not something you want to do.

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:

<Card title="Shrimp Tempura">


<img src="picOfShrimp.jpg" />
<div>
<p>Here are some words about tempura.</p>
<p>How can you go wrong with fried food?</p>
</div>
<a href="www.michaelnthiessen.com/shrimp-tempura">
Read more about Shrimp Tempura
</a>
</Card>

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.

You can’t do that with props.*

*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 :

VUE TIPS COLLECTION 40


<!-- You just have to *believe* it will work -->
<Button type="AWESOME" />

The component doesn’t allow for that...

<!-- Button.vue -->


<script setup>
defineProps<{
type: 'Primary' | 'Secondary' // AWESOME is not an option here
}>();
</script>

I’m not a psychic, and I’m guessing you aren’t either.

The solution to these problems?

I think I gave it away with my card example above...

...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.

41 SLOTS AND REUSABILITY


25.
Shorthand for named slots

Named slots also have a shorthand syntax, one that’s much nicer to look at.

Instead of writing this:

<DataTable>
<template v-slot:header>
<TableHeader />
</template>
</DataTable>

We can write this:

<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.

VUE TIPS COLLECTION 42


26.
Slot Transitions

It’s possible to use transitions with slot content, but there’s one key to making them work
smoothly:

<!-- SlotWithTransition.vue -->


<template>
<!-- Creating the base slot with a transition -->
<transition name="fade" mode="out-in">
<slot></slot>
</transition>
</template>

Always make sure that content provided to the slot is keyed.

This helps Vue keep track of when to trigger the transition:

<template>
<SlotWithTransition>
<div v-if="isThisTrue" key="true">
This is true.
</div>
<div v-else key="false">
This is false.
</div>
</SlotWithTransition>
</template>

43 SLOTS AND REUSABILITY


27.
Reusable Components Aren’t What You Think

Reusable components don’t have to be big or complex things.

I often make small and short components reusable.

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!

<!-- OverflowMenu.vue -->


<template>
<Menu>
<!-- Add a custom button to trigger our Menu -->
<template #button v-slot="bind">
<!-- Use bind to pass click handlers,
a11y attributes, etc. -->
<Button v-bind="bind">
<!-- Use our own "..." icon and no text
for this button -->
<template #icon>
<svg src="./ellipsis.svg" />
</template>
</Button>
</template>
</Menu>
</template>

VUE TIPS COLLECTION 44


Here we take a Menu component but add a ‘...’ (ellipsis) icon to the button that triggers it to
open.

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>

45 SLOTS AND REUSABILITY


28.
Splitting Slots

Let’s take a slot and split it into two slots:

<!-- Parent.vue -->


<template>
<Child>
<!--Splitting a slot into multiple requires
a conditional. This allows you to branch
into however many slots you want -->
<slot v-if="left" name="left" />
<slot v-else name="right" />
</Child>
</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:

<!-- Parent.vue -->


<template>
<Child>
<slot v-if="left" name="left">
<!-- Default content works like you'd expect -->
<div key="left">Default left</div>
</slot>
<slot v-else name="right">
<div key="right">Default Right</div>
</slot>
</Child>
</template>

VUE TIPS COLLECTION 46


29.
Improve reusability by converting template props
into slots

One kind of prop, a template prop, can be directly converted into slots without very much
work.

This makes your component more reusable.

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.

47 SLOTS AND REUSABILITY


These props can be directly replaced with slots:

<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.

With a prop we have to use the component like this:

<Button text="Click me" @click="handleClick" />

But with a slot, we can add in whatever we want:

<Button @click="handleClick">
Click on <strong>this</strong> button
</Button>

VUE TIPS COLLECTION 48


30.
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>

49 SLOTS AND REUSABILITY


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>

VUE TIPS COLLECTION 50


This is only 30 of 118 tips.

Get the full version of Vue Tips Collection 2 here.

michaelnthiessen.com/vue tips collection


-
-

You might also like