Vue.js Certification
Bootcamp
Welcome back!
👋
Day 2
- 7. Component Basics: Props
- 8. Component Basics: Events
- 9. Lifecycle hooks
- 10. Transitions
- 11. Slots
- 12. Watchers
- 13. Template refs
- 14. Plugins
Section Topics
But first...
Any questions about the homework?
Day 2
Let's start learning the material for
🚦
Component Fundamentals: Props
7
Section
So far we've done everything at the page level
But websites are made up of distinct components
Header
Filters
Courses
Course Card
<TheHeader>
<MainNav />
<SiteSearch />
<UserNav />
</TheHeader>
<CourseFilters/>
<TheCourses>
<CourseCard title="The Vue.js 3 Masterclass"/>
<CourseCard title="Nuxt.js 3 Fundamentals"/>
<CourseCard title="TypeScript with Vue.js 3"/>
<!-- ... -->
</TheCourses>
<TheFooter />
We can break site pieces up into separate files (SFCs) and assemble pages cleanly
<TheHeader>
<MainNav />
<SiteSearch />
<UserNav />
</TheHeader>
<CourseFilters/>
<TheCourses>
<CourseCard title="The Vue.js 3 Masterclass"/>
<CourseCard title="Nuxt.js 3 Fundamentals"/>
<CourseCard title="TypeScript with Vue.js 3"/>
<!-- ... -->
</TheCourses>
<TheFooter />
We can pass components arguments (ie. props) to make them flexible and reusable
<TheHeader>
<MainNav />
<SiteSearch />
<UserNav />
</TheHeader>
<CourseFilters/>
<TheCourses>
<CourseCard title="The Vue.js 3 Masterclass"/>
<CourseCard title="Nuxt.js 3 Fundamentals"/>
<CourseCard title="TypeScript with Vue.js 3"/>
<!-- ... -->
</TheCourses>
<TheFooter />
We must also sometimes communicate between components
(this pseudo code doesn't show how, we'll learn more in a minute)
How do we create these components?
(Let's look at a simple example)
A HelloWorld Component
- lives in the components directory by convention
- consists of 3 sections (already discussed SFC structure)
- this is a simple example. but can include complex markup, styles, or JS
// src/components/HelloWorld.vue
<script setup></script>
<template>
<div>
This is the component
</div>
</template>
<style>
.course-card{/**/}
</style>
// App.vue, MyPage.vue,
// or AnotherComponent.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<HelloWorld />
<!-- or -->
<HelloWorld></HelloWorld>
</template>
Use by importing into the script setup of another component
Then use as often as needed
<!-- App.vue, MyPage.vue
or AnotherComponent.vue -->
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<HelloWorld />
<HelloWorld />
<HelloWorld />
<HelloWorld />
</template>
Then use as often as needed
Result
<!-- App.vue, MyPage.vue
or AnotherComponent.vue -->
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<HelloWorld />
<HelloWorld />
<HelloWorld />
<HelloWorld />
</template>
Can make more flexible and re-usable with props
Let's look at a CourseCard component as an example
Let's look at a CourseCard component as an example
<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
console.log(props.title)
</script>
<template>
<div class="course-card">
<img :src="img">
<h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>
defineProps in script setup
Let's look at a CourseCard component as an example
<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
console.log(props.title)
</script>
<template>
<div class="course-card">
<img :src="img">
<h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>
capture return to use props in script section
Let's look at a CourseCard component as an example
<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
console.log(props.title)
</script>
<template>
<div class="course-card">
<img :src="img">
<h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>
access props directly in the template
Using the CourseCard
// App.vue, MyPage.vue,
// or AnotherComponent.vue
<script setup>
import CourseCard from './components/CourseCard.vue';
</script>
<template>
<CourseCard title="The Vue.js 3 Masterclass" img="/mc.jpg"/>
<CourseCard title="Nuxt.js 3 Fundamentals" img="/nuxt.jpg" />
<CourseCard title="TypeScript with Vue.js 3" img="ts.jpg" />
</template>
Using the CourseCard
Result
<script setup>
import CourseCard from './components/CourseCard.vue';
import {ref} from "vue";
const courses = ref([
{ title: "The Vue.js 3 Masterclass", img :"/mc.jpg" },
{ title: "Nuxt.js 3 Fundamentals", img: "/nuxt.jpg" },
{ title: "TypeScript with Vue.js 3", img: "/ts.jpg" }
])
const test = ref("");
</script>
<template>
<CourseCard
v-for="course in courses"
:key="course.title"
:title="course.title"
:img="course.img"
/>
</template>
Pass Dynamic Values as Props
Bind Multiple Properties Using an Object
<script setup>
import CourseCard from './components/CourseCard.vue';
import {ref} from "vue";
const courses = ref([
{ title: "The Vue.js 3 Masterclass", img :"/mc.jpg" },
{ title: "Nuxt.js 3 Fundamentals", img: "/nuxt.jpg" },
{ title: "TypeScript with Vue.js 3", img: "/ts.jpg" }
])
</script>
<template>
<CourseCard
v-for="course in courses"
:key="course.title"
v-bind="course"
/>
</template>
What all is possible with
defineProps?
defineProps
<script setup>
// no import of `defineProps` necessary
// it's compile time only
defineProps(['title']);
</script>
Can specify as many as needed
defineProps
<script setup>
defineProps(
['title', 'inProgress', 'percentWatched']
);
</script>
defineProps
Can specify their types
(shows runtime warning in console if wrong type provided)
<script setup>
defineProps({
title: String,
inProgress: Boolean,
percentWatched: String
tags: Array
});
</script>
defineProps
<script setup>
defineProps({
//...
tags: [Array, String]
});
</script>
Support multiple types with an array
Provide other options like required and default
defineProps
<script setup>
defineProps({
title: { type: String, required: true },
// boolean default is false
inProgress: { type: Boolean },
percentWatched: { type: Number, default: 0 },
// must define array and object defaults with a function
tags: { type: [Array, String], default: (rawProps)=> [] }
});
</script>
Include custom validation
defineProps
defineProps({
title: {
type: String,
required: true,
validator(value) {
return value.length < 50;
},
},
//...
});
Tips for working with props
Props bind data 1 way
const props = defineProps(['foo'])
// ❌ warning, props are readonly!
props.foo = 'bar'
Never mutate a prop
will log warning to console
const props = defineProps({
foo: Object
})
props.foo.someProperty = 'bar'
Not even objects or arrays
No warning but still highly discouraged
(no warning because it is unreasonably expensive for Vue to prevent such mutations)
Boolean props shorthand
<CourseCard inProgress />
<!--same as-->
<CourseCard :inProgress="true" />
Including a boolean prop with no value will imply `true`.
Must bind to support non-strings
<!-- without binding it's a string-->
<CourseCard percentWatched="0.5" />
<!-- this properly provides a number -->
<CourseCard :percentWatched="0.5" />
<!-- same applies to arrays and other non-strings -->
<!-- don't do this -->
<CourseCard tags="['tag-1', 'tag-2']" />
<!-- do this -->
<CourseCard :tags="['tag-1', 'tag-2']" />
Fallthrough attributes and props are different
<!-- CustomInput.vue -->
<script setup>
defineProps({
value: String
})
</script>
<template>
<input :value="value">
</template>
<!-- Parent.vue -->
<script setup>
import CustomInput from "@/components/CustomInput.vue";
</script>
<template>
<CustomInput placeholder="Name" value="" />
</template>
value is a prop
placeholder is a fallthrough attribute
Questions?
🙋🏾♀️
Exercise 7
👩💻👨🏽💻
Coffee Break
☕️
Component Fundamentals: Events
8
Section
Props are for passing data down to children components
Events are for passing data up to parent components
Imagine the User Card is Editable
(from the last challenge)
<script setup>
const props = defineProps({
username: {
type: String,
required: true,
},
});
</script>
<template>
<input v-model="username" >
<!-- ... -->
</template>
We can not directly mutate the prop
❌
So how do we change the user data in the parent from within the child?
We don't!
The parent is responsible for it's own data.
BUT we can emit an event so the parent can can choose how to respond
initial value is the username prop
emit an event on the input event
<script setup>
const props = defineProps({
username: {
type: String,
required: true,
},
});
</script>
<template>
<input
:value="username"
@input="$emit('update:username', $event.target.value)"
>
<!-- ... -->
</template>
How to Emit Anatomy
call $emit() function
pass a payload
name the event
@input="$emit('update:username', $event.target.value)"
Listen for the event in the parent
<UserProfileCard @update:username="user.username = $event" />
get the payload
just like native event listeners
Declaring Emitted Events
<!-- UserProfileCard -->
<script setup>
defineEmits(["update:username"]);
//...
</script>
<template>
<input
type="text"
:value="username"
@input="$emit('update:username', $event.target.value)"
/>
</template>
Declare all events in script section
Why declare emits?
Why declare emits?
- documents all the components events in 1 place
- let's you emit an event from within script setup section
- can provide payload validation
- If using TS, it brings error detection and autocompletion to events
- allows Vue to exclude known listeners from fallthrough attributes
Text
<!-- UserProfileCard -->
<script setup>
const emit = defineEmits(["update:username"]);
const updateUsername = (value)=> emit('update:username', value);
//...
</script>
<template>
<input
type="text"
:value="username"
@input="updateUsername($event.target.value)"
/>
</template>
emit an event from within script setup section
Text
<!-- UserProfileCard -->
<script setup>
const emit = defineEmits({
'update:username': (payload)=>{
return payload !== ''
}
})
//...
</script>
Payload validation
method with the name of the event
pass object instead of array
Text
<!-- UserProfileCard -->
<script setup>
const emit = defineEmits({
'update:username': (payload)=>{
return payload !== ''
}
})
//...
</script>
Payload validation
method takes the event payload
Return false for invalid or true for valid
Events Tips
Events Tips
- component events don't bubble (they're only accessible in the parent)
- payload isn't required
v-model on components
<!--MyComponent.vue-->
<script setup>
defineProps({
modelValue: String
});
defineEmits(['update:modelValue'])
</script>
<!--Parent.vue-->
<MyComponent modelValue="someData" @update:modelValue="someData = $event" />
v-model on components
<!--MyComponent.vue-->
<script setup>
defineProps({
modelValue: String
});
defineEmits(['update:modelValue'])
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>
Shorthand when Used
v-model on components
<!--MyComponent.vue-->
<script setup>
const modelValue = defineModel()
// modelValue.value = "newValue" // emits update:modelValue
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>
Shorthand when Defined
v-model on components
<!--MyComponent.vue-->
<script setup>
defineProps({
title: String
});
defineEmits(['update:title'])
</script>
<!--Parent.vue-->
<MyComponent title="someData" @update:title="someData = $event" />
v-model on components
<!--MyComponent.vue-->
<script setup>
defineProps({
title: String
});
defineEmits(['update:title'])
</script>
<!--Parent.vue-->
<MyComponent v-model:title="someData"/>
Shorthand when Used
v-model on components
<!--MyComponent.vue-->
<script setup>
const title = defineModel("title")
// title.value = "newValue" // emits update:title
</script>
<!--Parent.vue-->
<MyComponent v-model:title="someData"/>
Shorthand when defined
Questions?
🙋🏾♀️
Exercise 8
👩💻👨🏽💻
Lifecycle Hooks
All components go through various steps throughout their lifespan
We can run custom code at different points of that lifespan with lifecycle hooks
This is what the lifecycle looks like
Let's zoom into the different hooks
Let's breakdown each step
Why?
Just do it at the root level of script setup
When using script setup we don't really use:
- beforeCreate
- created
- beforeMount
- elements exist in DOM
- only runs in browser (no SSR)
Mounted Practical Use Cases
- add manual event listeners to the DOM (sometimes can't use an event because the element to listen to is outside of the component (like the window)
- fetch data only on the client (in SSR applications)
- Check the width of a rendered DOM element to use for some calculation
- access the DOM state before Vue updates the DOM
- safe to modify component state
- I rarely use
- called after any DOM update of the component
- ⚠️ Do not mutate component state in the updated hook
- Useful for direct DOM access
- called right before a component is unmounted
- the component instance is still fully functional
- I rarely use
- after the component and it's children are removed from DOM
- all reactive effects are stopped
- useful for cleaning up side effects
Unmounted Use Cases
- cleaning up any manual event listeners
- cleaning up any intervals
- cleaning up any subscriptions to server events
Usage with script setup
import { onMounted, onUnmounted, onUpdated } from "vue";
onMounted(()=>{
// do whatever
})
first import from vue
Then call the function with a callback
Call Syncronously
import { onMounted, onUnmounted, onUpdated } from "vue";
onMounted(()=>{
// do whatever
})
⚠️ NOTE: you should call the lifecycle hooks syncronously inside of setup
setTimeout(() => {
onMounted(() => {
// this won't work.
})
}, 100)
This would not work
Call Syncronously
Call Syncronously
- Doesn't mean you can't use lifecycle hooks in a composable
- Just make sure it's called synchronously in the composable and the composable is called synchronously in the component
A Practical lifecycle hooks example
Questions?
🙋🏾♀️
Exercise 9
👩💻👨🏽💻
Transitions
Section
10
Vue has a built-in <Transition> component for handling smooth transitions between various states
Use it to transition a single element entering or leaving the page
Also works with v-show
Or transitioning between multiple elements as long as only 1 is visble at a time
Transition can be different styles based on CSS
- First examples faded in and out
- The last example, elements moved up and faded out
🤔 How does that work?
🤔 How does that work?
These styles power the fade transition
Vue strategically adds these classes to the element
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
🤔 How does that work?
Added before the element is inserted, removed one frame after the element is inserted.
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
🤔 How does that work?
Applied during the entire entering phase.
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
This class can be used to define the duration, delay and easing curve for the entering transition
🤔 How does that work?
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
Ending state for enter. Added one frame after the element is inserted
Not needed in this example because opacity 1 is the elements default
🤔 How does that work?
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
The same thing happens on leave
So what would fading in and moving down from the top look like?
So what would fade in down look like?
.v-enter-active,
.v-leave-active {
transition: all 0.5s;
}
.v-enter-from,
.v-leave-to {
transform: translateY(-50px);
opacity: 0;
}
So what would fade in down look like?
.v-enter-active,
.v-leave-active {
transition: all 0.5s;
}
.v-enter-from,
.v-leave-to {
transform: translateY(-50px);
opacity: 0;
}
A slight tweak can give a different effect
.v-enter-active,
.v-leave-active {
transition: all 0.5s;
}
.v-enter-from{
transform: translateY(-50px);
opacity: 0;
}
.v-leave-to {
transform: translateY(50px);
opacity: 0;
}
The <Transition> component has lots more to offer
- Named transitions
- JavaScript Hooks
- Transitions on appear
- Transition modes
- but we've covered enough to get you through the exam
1 More Component That Helps with Transitions
<TransitionGroup>
<TransitionGroup>
animates the insertion, removal, and order change of elements or components that are rendered in a list.
Example
Questions?
🙋🏾♀️
Exercise 10
👩💻👨🏽💻
Slots
Section
11
Props are great for making flexible components
Sometimes they fall a little short though
Button Example
<MyButton label="Click Me" />
Imagine we have a button that starts off simple
Button Example
But sometimes we'd like to include an icon
<MyButton
label="Click Me"
icon="mdi:home"
/>
Button Example
<MyButton
label="Click Me"
icon="mdi:home"
:boldFirstWord="true"
/>
And for this one, we'd like to bold the first word
We're doing a lot to customize the HTML that goes inside the button
That's what slots are good for!
<MyButton>
<Icon icon="mdi:home"></Icon>
<span><strong>Click</strong> Me</span>
</MyButton>
Let the parent customize what goes inside based on the use case
How do we write the component to handle this sytnax?
<!--MyButton.vue -->
<template>
<button class="btn">
<slot></slot>
</button>
</template>
provide a slot outlet that indicates where the parent-provided slot content should be rendered
<!--MyButton.vue -->
<template>
<button class="btn">
<slot></slot>
</button>
</template>
so with this definition
and this usage
<MyButton>
<Icon icon="mdi:home"></Icon>
<span><strong>Click</strong> Me</span>
</MyButton>
<button class="btn">
<svg>
<!-- the rendered Icon component here,
internals not important -->
</svg>
<span><strong>Click</strong> Me</span>
</button>
This is rendered
<!--MyButton.vue -->
<template>
<button class="btn">
<slot>Default Label</slot>
</button>
</template>
provide default slot content
with this definition
and this usage
<MyButton/>
<button class="btn">Default Label</button>
This is rendered
<!--MyButton.vue -->
<template>
<button class="btn">
<slot>Default Label</slot>
</button>
</template>
Name slots to provide multiple outlets
<!-- SiteLayout.vue-->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
Pass content to the outlets in the parent with template and v-slot
<template>
<!-- Parent.vue-->
<SiteLayout>
<template v-slot:header> Hello Header </template>
Hello default
<template #footer> Hello Footer </template>
</SiteLayout>
</template>
# shorthand
default doesn't need template tags
<template>
<!-- Parent.vue-->
<SiteLayout>
<template v-slot:header> Hello Header </template>
<template #default>Hello default</template>
<template #footer> Hello Footer </template>
</SiteLayout>
</template>
Can optionally use template for default slot
The rendered output
<div class="container">
<header>
Hello header
</header>
<main>
Hello default
</main>
<footer>
Hello Footer
</footer>
</div>
<!-- Parent.vue-->
<script setup>
import {ref} from "vue";
const headerSlotName = ref("header");
const footerSlotName = ref("foooter");
</script>
<template>
<SiteLayout>
<template v-slot:[headerSlotName]> Hello Header </template>
<template #default>Hello default</template>
<template #[footerSlotName]> Hello Footer </template>
</SiteLayout>
</template>
Can set slots in parent dynamically with []
<!-- SiteLayout.vue-->
<script setup>
import {ref} from "vue"
const dynamicHeaderSlotName= ref("header")
</script>
<div class="container">
<header>
<slot :name="dynamicHeaderSlotName"></slot>
</header>
<!-- .... -->
</div>
Can even make slot names dynamic for the defintion
Scoped Slots
Let's look at
Slot content has access to the data scope of the parent component
<!-- MyParent.vue -->
<script setup>
const label = ref("Click Me")
</script>
<MyButton>
<Icon icon="mdi:home"></Icon>
<span>{{ label }}</span>
</MyButton>
Slot content does not have access to the child component's data.
<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>
<template>
<button class="btn" >
<slot></slot>
</button>
</template>
<!-- MyParent.vue -->
<MyButton>
<Icon icon="mdi:home"></Icon>
<span>{{buttonData}}</span>
</MyButton>
❌
<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>
<template>
<button class="btn" >
<slot></slot>
</button>
</template>
Can we expose buttonData to the parent's slot content?
🤔
<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>
<template>
<button class="btn" >
<slot :data="buttonData"></slot>
</button>
</template>
Yes! Just pass it like a prop
<!-- MyParent.vue-->
<template>
<ButtonWithData>
<template #default="{ data }">
<span>
{{ data }} <!-- some random data -->
</span>
</template>
</ButtonWithData>
</template>
In the parent all data given as the value to the slot name
<!-- MyButton.vue-->
<slot :data="buttonData" otherData="something else"></slot>
<!-- MyParent.vue-->
<template>
<ButtonWithData>
<template #default="{ data, otherData }">
<span>
{{ data }} <!-- some random data -->
{{ otherData }} <!-- something else -->
</span>
</template>
</ButtonWithData>
</template>
Can pass multiple pieces of data
using template works with default and named slots
<!-- MyParent.vue-->
<template>
<ButtonWithData>
<template #default="{ data, otherData }">
<span>
{{ data }} <!-- some random data -->
{{ otherData }} <!-- something else -->
</span>
</template>
</ButtonWithData>
</template>
<!-- MyParent.vue-->
<template>
<ButtonWithData v-slot="{ data, otherData }">
<span>
{{ data }} <!-- some random data -->
{{ otherData }} <!-- something else -->
</span>
</ButtonWithData>
</template>
can shorten for default slots
<!-- MyParent.vue-->
<template>
<ButtonWithData #="{ data, otherData }">
<span>
{{ data }} <!-- some random data -->
{{ otherData }} <!-- something else -->
</span>
</ButtonWithData>
</template>
or even just the #
<!-- MyParent.vue-->
<template>
<ButtonWithData #="{ data, otherData }">
<span>
{{ data }} <!-- some random data -->
{{ otherData }} <!-- something else -->
</span>
</ButtonWithData>
</template>
⚠️ WARNING: this only works when NO named slots
Scoped Slots Practical Example
Questions?
🙋🏾♀️
Exercise 11
👩💻👨🏽💻
Coffee Break
☕️
Watchers
12
Section
Vue provides a couple different functions for watching reactive data and then performing side effects when that data changes
Some practical use cases:
- Showing a notification when some data is freshly updated
const myDataFromTheBackend = ref({...})
watch(myDataFromTheBackend, {
alert("Hello, your friend just updated some data")
}, { deep: true })
Some practical use cases:
2. Emitting an event when local data changes
const props = defineProps({
value: String
})
const emit = defineEmits(['update:value'])
const localValue = ref(props.value)
watch(localValue, ()=> emit('update:value', localValue))
Some practical use cases:
3. Making a fetch request when data changes
const posts = ref([...])
const sortOrder = ref("desc");
watch(sortOrder, fetchPosts)
function fetchPosts(){
// call to api for posts and set data
posts.value = postsFromServer
}
Some practical use cases:
4. Using a Browser API
const cookieAccepted = ref(localStorage.getItem() || false);
watch(cookieAccepted, (accepted)=>{
localStorage.setItem("cookieAccepted", accepted)
})
- watch()
- watchEffect()
The 2 watcher functions
watch()
watchEffect()
- Used to perform side effects
- manually register reactive dependencies
- lazy by default
- auto cleaned up on component unmount
- Used to perform side effects
- reactive dependencies in callback automatically registered
- eager by default
- auto cleaned up on component unmount
watch() example
const myTeamsScore = ref(7);
watch(
myTeamsScore,
(myScore) => alert("My Score has changed")
);
data to watch
(can be a reactive ref)
watch() example
const scores = reactive({ myTeam: 7 });
watch(
scores,
(scores) => alert("My Score has changed")
);
(or can be reactive)
const myTeamsScore = ref(7);
const scoreDoubled = computed(()=> myTeamsScore.value * 2)
watch(
scoreDoubled,
(doubled) => alert("My Score has changed")
);
watch() example
(or a computed prop)
watch() example
const myTeamsScore = ref(7);
watch(
myTeamsScore,
(myScore) => alert("My Score has changed")
);
Callback function
data to watch
watch() example
const myTeamsScore = ref(7);
watch(
myTeamsScore,
(myScore) => alert("My Score has changed")
);
updated value is passed (not a ref)
watch() example
const myTeamsScore = ref(7);
const otherTeamsScore = ref(0);
watch([myTeamsScore, otherTeamsScore],
([myScore, otherScore]) => {
console.log(myScore, otherScore); // 7, 1
});
watch multiple values by passing an array of refs
watch() example
Then values are passed as an array to the callback in the same order
(can de-structure)
const myTeamsScore = ref(7);
const otherTeamsScore = ref(0);
watch([myTeamsScore, otherTeamsScore],
([myScore, otherScore]) => {
console.log(myScore, otherScore); // 7, 1
});
watch() example
const scores = ref([7, 0]);
watch(
scores,
(scores) => {
console.log(scores); // [7, 1]
},
);
Can also watch a ref that is an array
watch() example
But you must provide the deep option
const scores = ref([7, 0]);
watch(
scores,
(scores) => {
console.log(scores); // [7, 1]
},
{
deep: true
}
);
watch() example
const scores = ref({
myTeamsScore: 7,
otherTeamsScore: 3
});
watch(
scores,
(scores) => {
console.log(scores);
},
{
deep: true
}
);
Same applies to an object
watch() example
const scores = ref({
myTeamsScore: 7,
otherTeamsScore: 3
});
watch(
()=> scores.value.myTeamsScore,
(myTeamsScore) => {
console.log(myTeamsScore);
}
);
Watch only what's necessary in an object using a getter
(without the deep option)
const myTeamsScore = ref(7);
watch(
myTeamsScore,
(myScore) => alert("My Score has changed")
);
watch() example
Watch is lazy so the callback will only run after myTeamsScore changes
const myTeamsScore = ref(7);
watch(
myTeamsScore,
(myScore) => alert("My Score has changed"),
{
immediate: true
}
);
watch() example
Use immediate option to run callback as soon as watcher is defined
watchEffect()
const myTeamsScore = ref(7);
watchEffect(() => {
console.log(myTeamsScore.value);
});
watchEffect() example
Only provide a callback as dependencies are automatically registered
const myTeamsScore = ref(7);
watchEffect(() => {
// logs 7 immediately
console.log(myTeamsScore.value);
});
watchEffect() example
is eager, so callback runs immediately
const myTeamsScore = ref(7);
watchEffect(() => {
// logs 7 immediately
console.log(myTeamsScore.value);
}, {
immediate: false
});
watchEffect() example
cannot make lazy!
❌
const footballScore = ref({
myTeam: 7,
otherTeam: 0,
});
watchEffect(
() => console.log(footballScore.value);
);
watchEffect() example
always watches deeply
const footballScore = ref({
myTeam: 7,
otherTeam: 0,
});
watchEffect(
() => console.log(footballScore.value),
{
deep: false
}
);
watchEffect() example
CANNOT turn off deep
❌
Both can be manually stopped
// will onlyl run once
// since it unwatches the first time
const unwatch = watchEffect(() =>{
console.log(myTeamsScore.value)
unwatch()
});
//same here
const unwatch = watch(myTeamScore, () =>{
console.log(myTeamsScore.value)
unwatch()
});
Could also unwatch on some user event
const unwatch = watchEffect(() =>{
console.log(myTeamsScore.value)
});
function onClick(){
unwatch()
}
import { watchEffect } from 'vue'
// this one will be automatically stopped
watchEffect(() => {})
// ...this one will not!
let unwatch;
setTimeout(() => {
unwatch = watchEffect(() => {})
}, 100)
// manual cleanup here
onUnmounted(unwatch)
Can also manually clean up asynchronously defined watchers
import { watchEffect } from 'vue'
// this one will be automatically stopped
watchEffect(() => {})
// ...this one will not!
let unwatch;
setTimeout(() => {
unwatch = watchEffect(() => {})
}, 100)
// manual cleanup here
onUnmounted(unwatch)
Can also manually clean up asynchronously defined watchers
This is very rare
Questions?
🙋🏾♀️
Exercise 12
👩💻👨🏽💻
Template Refs
13
Section
Template refs give us direct access to DOM elements
<script setup>
import { onMounted, ref } from "vue";
const el = ref();
onMounted(() => {
console.log(el.value.innerText);
});
</script>
<template>
<div>
<p ref="el">Hello World</p>
</div>
</template>
This is the template ref
It refers to this DOM element
Once the component mounts we can use the DOM element just like we would with vanilla JS
Can also get an array of template refs with v-for
<script setup>
import { ref, onMounted } from "vue";
const elements = ref([]);
onMounted(() => {
elements.value.forEach((el) => {
console.log(el);
});
});
</script>
<template>
<div v-for="n in 10" :key="n" ref="elements">
{{ n }}
</div>
</template>
Practical Use Cases
- reading DOM element properties like their clientHeight or clientWidth
- calling DOM element methods like blur and focus
Template Refs can also directly access child components
<!--ModalOverlay.vue-->
<script setup>
import { defineExpose, ref } from "vue";
const active = ref(false);
function toggle() {
active.value = !active.value;
}
defineExpose({
toggle,
});
</script>
<template>
<div v-if="active">...</div>
</template>
Let's say we have a modal overlay component that exposes a toggle function
In the parent we can use a template ref to access the ModalOverlay and call it's exposed function
<script setup lang="ts">
import { ref } from "vue";
import ModalOverlay from "@/components/ModalOverlay.vue";
const modal = ref();
</script>
<template>
<ModalOverlay ref="modal" />
<button @click="modal.toggle()">Toggle Modal</button>
</template>
Questions?
🙋🏾♀️
Exercise 13
👩💻👨🏽💻
🎉
That's all for day 2
Homework
Continue practicing the concepts we learned today if you choose
Official Training Module
Resources for Helping to Complete Homework
- There are links in the training module to the relevant Vue.js docs
- Vue 3 Composition API Course
🙋🏾♀️
Any final questions?
👋
See you next week!
Bootcamp Level 1 Day 2
By Daniel Kelly
Bootcamp Level 1 Day 2
- 282