Vue.js Certification
Bootcamp
Welcome back!
👋
Section Topics
But first...
Any questions about the homework?
Let's start learning the material for
🚦
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
// 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
// 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>
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>
<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
<script setup>
// no import of `defineProps` necessary
// it's compile time only
defineProps(['title']);
</script>
Can specify as many as needed
<script setup>
defineProps(
['title', 'inProgress', 'percentWatched']
);
</script>
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>
<script setup>
defineProps({
//...
tags: [Array, String]
});
</script>
Support multiple types with an array
Provide other options like required and default
<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({
title: {
type: String,
required: true,
validator(value) {
return value.length < 50;
},
},
//...
});
const props = defineProps(['foo'])
// ❌ warning, props are readonly!
props.foo = 'bar'
will log warning to console
const props = defineProps({
foo: Object
})
props.foo.someProperty = 'bar'
No warning but still highly discouraged
(no warning because it is unreasonably expensive for Vue to prevent such mutations)
<CourseCard inProgress />
<!--same as-->
<CourseCard :inProgress="true" />
Including a boolean prop with no value will imply `true`.
<!-- 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
☕️
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
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
<!--MyComponent.vue-->
<script setup>
defineProps({
modelValue: String
});
defineEmits(['update:modelValue'])
</script>
<!--Parent.vue-->
<MyComponent modelValue="someData" @update:modelValue="someData = $event" />
<!--MyComponent.vue-->
<script setup>
defineProps({
modelValue: String
});
defineEmits(['update:modelValue'])
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>
Shorthand when Used
<!--MyComponent.vue-->
<script setup>
const modelValue = defineModel()
// modelValue.value = "newValue" // emits update:modelValue
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>
Shorthand when Defined
<!--MyComponent.vue-->
<script setup>
defineProps({
title: String
});
defineEmits(['update:title'])
</script>
<!--Parent.vue-->
<MyComponent title="someData" @update:title="someData = $event" />
<!--MyComponent.vue-->
<script setup>
defineProps({
title: String
});
defineEmits(['update:title'])
</script>
<!--Parent.vue-->
<MyComponent v-model:title="someData"/>
Shorthand when Used
<!--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
👩💻👨🏽💻
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:
Mounted Practical Use Cases
import { onMounted, onUnmounted, onUpdated } from "vue";
onMounted(()=>{
// do whatever
})
first import from vue
Then call the function with a callback
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
A Practical lifecycle hooks example
Questions?
🙋🏾♀️
Exercise 9
👩💻👨🏽💻
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
🤔 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
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
👩💻👨🏽💻
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
<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
☕️
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:
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)
})
The 2 watcher functions
watch()
watchEffect()
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
👩💻👨🏽💻
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
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
🙋🏾♀️
Any final questions?
👋
See you next week!