@vueschool_io
vueschool.io
Welcome
π
Vue 3 and the Composition API
π You will learn
Essential Composition API syntax and functions
- Script setup
- When and how you should use the Composition API
- Using composition functions for logic re-use
- Organizing code by feature
- Using 3rd party composables
- Lifecycle Hooks with the Composition API
With the experience and codes from these workshops you will be able to use the Composition API in your apps right away!
Setup Exercise Project Locally
All exercises can be completed on StackBlitz as well
β° 10 mins
What is the Composition API?
What is the Composition API?
What do you think?
Answer in the chat
Vue 2 used the Options API
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Vue 3 introduces the Composition API
const loading = ref(false)
const count = ref(0)
const user = reactive({})
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Define reactive data with
ref
or
reactive
const loading = ref(false)
const count = ref(0)
const user = reactive({})
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Should I use
ref
or
reactive
?
For now, just use ref.
const double = computed(()=> count.value * 2)
const fullname = computed(()=>({ /* ... */ }))
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Define derived data with
computed
const increment = ()=> count.value++
function fetchUser(){ /* ... */ }
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Define methods as
functions
import { ref, computed, watch } from 'vue'
No longer passing options INTO Vue
Instead we're getting reactive functions OUT OF Vue
Can use reactivity anywhere
outside components
outside a Vue app
inside components
Example of using reactivity outside of a Vue app
Example of using reactivity inside of a Vue component
Questions?
ππΎββοΈ
Exercises 1 & 2
ποΈββοΈ ποΈ
<script setup>
So far, we've used the setup option without a build tool but the experience isn't great
Must return everything from the setup option
<script>
export default{
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
}
</script>
Plus there's a lot of unnecessary indentation
<script>
export default{
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
}
</script>
<script setup>
compile-time syntactic sugar for using Composition API
inside Single-File Components (SFCs)
<script setup>
- must use a build tool like Vite or Webpack
- Cannot use with Vue from a CDN
- Composition API === use a build tool
How does script
setup work?
How does script
setup work?
<script>
export default{
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
}
</script>
How does script
setup work?
<script setup>
export default{
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
}
</script>
Add setup attribute to the script tag
How does script
setup work?
<script setup>
export default{
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
}
</script>
Remove the options object
How does script
setup work?
<script setup>
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
</script>
Remove the options object
How does script
setup work?
<script setup>
setup(){
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
}
</script>
Remove the setup option
How does script
setup work?
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
</script>
Remove the setup option
How does script
setup work?
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
return {
loading,
products,
numberOfProducts,
fetchProducts
};
</script>
Remove all the returns
How does script
setup work?
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
Remove all the returns
How does script
setup work?
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
How does script
setup work?
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
Ahhh... that's nice!
More about
<script setup>
Convention = Script setup ABOVE template
<script setup>
//...
</script>
<template>
//...
</template>
<template>
//...
</template>
<script>
export default{}
</script>
β
β
Import Components to Register them
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
<script setup>
import HelloWorld from "@/components/HelloWorld.vue";
</script>
β
β
Define directives by prefixing with v
<script>
const autoFocus = {
mounted: (el) => el.focus()
}
export default {
directives: [ autoFocus ]
};
</script>
<script setup>
const vAutoFocus = {
mounted: (el) => el.focus()
}
</script>
β
β
<template>
<input v-auto-focus />
</template>
Define props
<script>
export default {
props: {
product: Object,
},
created(){
console.log(this.product)
}
};
</script>
<script setup>
const props = defineProps({
product: Object,
});
console.log(props.product)
</script>
β
β
define with defineProps and access values on the return
Define emits
<script>
export default {
emits: ['submit'],
created(){
this.$emit("submit")
}
}
</script>
<script setup>
const emit = defineEmits(['submit'])
emit('submit')
</script>
β
β
define with defineEmits and the emit function is returned
Define Template Refs
<script>
export default{
onMounted(){
this
.$refs
.input
.focus()
}
}
</script>
<template>
<input ref="input" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
// declare a ref to hold the element reference
// the name must match template ref value
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
β
β
Questions?
ππΎββοΈ
Exercise 3
ποΈββοΈ ποΈ
Recap
- Vue Composition API is an alternate way of interacting with Vue's reactivity system
- It can be used within components and outside of components
- Reactive data is defined with ref or reactive
- Computed props are defined with computed function
- Methods are just normal functions
- Script setup makes the syntax less verbose
Why Composition API?
Code Organization
Logic Reuse
Improved
TypeScript Support
Code Organization
Organize by OPTIONS
<script>
export default {
data() {
return {
loading: false,
products: [],
sortBy: "price",
desc: false,
};
},
computed: {
numberOfProducts() {...},
sortedProducts() {...},
},
methods: {
fetchProducts() {...},
setSortDirection() {...},
},
};
<script>
Product Fetching
Product Sorting
Product Meta
Features are all mixed up
Organize by LOGICAL CONCERN
<script setup>
// loading products
const loading = ref(true);
const products = ref([]);
async function fetchProducts() { ... }
fetchProducts();
// soring products
const sortBy = ref("price");
const desc = ref(false);
const sortedProducts = computed(...);
// meta
const numberOfProducts = computed(...);
</script>
Product Fetching
Product Sorting
Product Meta
Features grouped together
Which do you prefer?
<script setup>
// loading products
const loading = ref(true);
const products = ref([]);
async function fetchProducts() { ... }
fetchProducts();
// soring products
const sortBy = ref("price");
const desc = ref(false);
const sortedProducts = computed(...);
// meta
const numberOfProducts = computed(...);
</script>
<script>
export default {
data() {
return {
loading: false,
products: [],
sortBy: "price",
desc: false,
};
},
computed: {
numberOfProducts() {...},
sortedProducts() {...},
},
methods: {
fetchProducts() {...},
setSortDirection() {...},
},
};
<script>
β
β
Especially important for long/complex components
Questions?
ππΎββοΈ
Exercise 4
ποΈββοΈ ποΈ
Skip
Logic Reuse
Logic Reuse
<script setup>
import {ref} from "vue"
const count = ref(0);
const increment = () => count.value++;
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Button Component From Earlier
Logic Reuse
<script setup>
import {ref} from "vue"
// composable
const useCounter = ()=>{
const count = ref(0);
const increment = () => count.value++;
return { count, increment }
}
const {increment, count} = useCounter()
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Extract to a composition function (composable)
Logic Reuse
<script setup>
import {ref} from "vue"
// composable
const useCounter = (initial = 0)=>{
const count = ref(initial);
const increment = () => count.value++;
return { count, increment }
}
const {increment, count} = useCounter(40)
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Flexibility with arguments
Logic Reuse
// src/composables/useCounter.js
import {ref} from "vue"
export const useCounter = (initial = 0)=>{
const count = ref(initial);
const increment = () => count.value++;
return { count, increment }
}
Extract to it's own file that can be imported in any component
<script setup>
import {ref} from "vue"
import { useCounter } from "@/composables/useCounter"
const {increment, count} = useCounter(40)
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Logic Reuse
More practical example: useFetch composable
// @/composables/useFetch.js
import { ref } from 'vue';
export const useFetch = (url) => {
const loading = ref(true);
const data = ref(null);
fetch(url).then(async (res) => {
data.value = await res.json();
loading.value = false;
});
return { data, loading };
};
Logic Reuse
More practical example: useFetch composable
<script setup>
import { useFetch } from '@/composables/useFetch';
const { data, loading } = useFetch(
'https://someApiEndpoint.com'
);
</script>
<template>
<p v-if="loading">loading...</p>
<pre>{{ data }}</pre>
</template>
Logic Reuse
Use multiple times in same component without collisions
<script setup>
import { useFetch } from '@/composables/useFetch';
const { data: products, loading: loadingProducts } = useFetch(
'https://someApiEndpoint.com'
);
const { data: users, loading: loadingUsers } = useFetch(
'https://someOtherApiEndpoint.com'
);
</script>
Logic Reuse
Can also make the composable accept a reactive URL
// @/composables/useFetch.js
import { ref } from 'vue';
export const useFetch = (url) => {
const loading = ref(true);
const data = ref(null);
fetch(url).then(async (res) => {
data.value = await res.json();
loading.value = false;
});
return { data, loading };
};
Logic Reuse
Can also make the composable accept a reactive URL
// @/composables/useFetch.js
import { ref, toRef, watch, computed } from 'vue';
export const useFetch = (url) => {
// handle a reactive ref, a string, or a getter function ()=> url
const URL = toRef(url)
const loading = ref(true);
const data = ref(null);
// wrap request in function to call right away
// and whenever URL changes
function makeRequest() {
loading.value = true;
fetch(URL.value).then(async (res) => {
data.value = await res.json();
loading.value = false;
});
}
makeRequest();
// watch URL for changes and make request
watch(URL, makeRequest);
return { data, loading };
};
Logic Reuse
Can also make the composable accept a reactive URL
<script setup>
import {ref} from "vue"
import { useFetch } from '@/composables/useFetch';
const url = ref("https://someApiEndpoint.com")
const { data, loading } = useFetch(url);
</script>
Logic Reuse
Can also make the composable accept a reactive URL
<script setup>
import { ref, computed } from "vue"
import { useFetch } from '@/composables/useFetch';
const query = ref("") // whatever search string
const url = computed(()=>`https://someApiEndpoint.com?q=${query}`)
const { data, loading } = useFetch(url);
</script>
Logic Reuse
An example of a pointer position composable
// @/composables/usePointer.js
import { ref, onUnmounted } from 'vue';
export const usePointer = () => {
const x = ref(0);
const y = ref(0);
// handler to call when the mouse move event fires
const handler = (e) => {
x.value = e.x;
y.value = e.y;
};
// add handler to mousemove event
window.addEventListener('mousemove', handler);
// clean up when component is umnounted
onUnmounted(() => {
window.removeEventListener('mousemove', handler);
});
return { x, y };
};
Logic Reuse
An example of a pointer position composable
<script setup>
import { ref, computed } from 'vue';
import { usePointer } from './composables/usePointer';
const { x, y } = usePointer();
</script>
<template>
<h1>x: {{ x }}, y: {{ y }}</h1>
</template>
Logic Reuse
Vue 2 did provide alternatives for logic re-use but they all had their drawbacks
- Mixins
- Higher Order Components
- Renderless Components
Logic Reuse
Vue 2 did provide alternatives for logic re-use by they all had their drawbacks
Questions?
ππΎββοΈ
Exercise 5&6
ποΈββοΈ ποΈ
Logic Reuse
Off the Shelf Composables
VueUse
- State
- Elements
- Browser
- Sensors
- Network
- Animation
- Component
- Watch
- Reactivity
- Array
- Time
- Utilities
200+
VueUse
npm i @vueuse/core
VueUse
<script setup>
import {useMouse} from "@vueuse/core"
const { x, y } = useMouse()
</script>
<template>
<div>pos: {{x}}, {{y}}</div>
</template>
useMouse
VueUse
<script setup>
import { useFetch } from '@vueuse/core'
const { isFetching, error, data } = await useFetch(url)
</script>
useFetch
VueUse
<script setup>
import { useLocalStorage } from '@vueuse/core';
const framework = useLocalStorage('framework', null);
</script>
<template>
<input type="text" v-model="framework" />
</template>
useLocalStorage
VueUse
VueUse
Let's visit the website and see what else is available
Questions?
ππΎββοΈ
Exercise 7
ποΈββοΈ ποΈ
Recap
- We've learned how to define reactive data, computed data, watchers, and methods in the composition API
- Composition API is useful for better code organization
- Also helpful for logic re-use
- VueUse is a popular library of 200+ off-the-shelf composables
LifeCycle Hooks
LifeCycle Hooks
// MyComponent.vue
<script setup>
import { onMounted, onUnmounted } from "vue";
onMounted(()=>{
// do things
})
</script>
Use in components
LifeCycle Hooks
// MyComponent.vue
<script setup>
import { onCreated } from "vue";
// just do what you'd normally do in created
// directly in script setup
</script>
onCreated function does not exist
β
LifeCycle Hooks
onCreated function does not exist
LifeCycle Hooks
// useMyComposable.js
import { onMounted, onUnmounted } from "vue";
export function useMyComposable(){
onMounted(()=>{
// do things
})
}
Use in composables and will fire based on the lifecycle of the component used in
LifeCycle Hooks
// useMyComposable.js
import { onMounted, onUnmounted } from "vue";
export function useMyComposable(){
onMounted(()=>{
// DOM elements ready
})
}
onMounted is useful for ensuring component elements are available in the DOM and can be directly accessed/manipulated
LifeCycle Hooks
// useMyComposable.js
import { onMounted, onUnmounted } from "vue";
export function useMyComposable(){
onUnmounted(()=>{
// clean up: event listeners,
// intervals, etc
})
}
onUnmounted is useful for cleaning up event listeners, intervals, etc to prevent memory leaks
Questions?
ππΎββοΈ
Exercise 8
ποΈββοΈ ποΈ
Skip
Ref vs Reactive
The Reactive Function
<script setup>
const framework = reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
})
framework.name // no need for .value
</script>
Alternative way to declare reactive data
Ref Works on Primitives
Reactive does not
// β
ref works
const framwork = ref('Vue')
const isAwesome = ref(true)
const created = ref(2014)
// β won't work
const framwork = reactive('Vue')
const isAwesome = reactive(true)
const created = reactive(2014)
Can Re-assign whole ref values but not reactive
// β
ref works
let posts = ref(['post 1', 'post 2'])
posts.value = ['post 3', 'post 4']
// β won't work
let posts = reactive(['post 1', 'post 2'])
posts = ['post 3', 'post 4']
Ref requires .value reactive does not
// ref
const framework = ref({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
})
β¬οΈ
console.log(framework.value.name)
// reactive
const framework = reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
})
console.log(framework.name)
Can destructure object of refs, cannot destructure a reactive object
// β
ref works
const framework = {
name: ref('Vue'),
author: ref('Evan You'),
tags: ref(['javascript', 'vue'])
}
const { name } = framework
// β reactive doesn't
const framework = reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
})
// name is no longer reactive
const { name } = framework
Can convert reactive object to refs
// β
will work
const framework = toRefs(reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
}))
const { name } = framework
toRefs useful when exposing data defined with reactive from a composable
// @/composables/useFetch
import { reactive } from "vue"
export const useFetch = (url)=>{
const state = reactive({
loading: true,
data: null
})
//...
return toRefs(state)
}
<script setup>
import { useFetch } from "@/composables/useFetch"
const {data, loading} = useFetch("https://myApiEndpoint.com")
</script>
Which to use? π€
- Sometimes you have no choice
- Must use refs for primitives
- Must use ref when replacing whole value
- Otherwise it's personal preference
- Many choose to use ref for everything
Ref or Reactive
Which to use? π€
- Sometimes you have no choice
- Must use refs for primitives
- Must use ref when replacing whole value
- Otherwise it's personal preference
- Many choose to use ref for everything
Ref or Reactive
Vue Docs recommend
to use ref everywhere
Reactive State Helper Functions
Reactive State Helper Functions
let foo = ref("");
if (isRef(foo)) {
// true
}
isRef()
Checks if a value is a ref object.
Reactive State Helper Functions
let foo = reactive({...})
if (isReactive(foo)) {
// true
}
isReactive()
Checks if an object is a proxy created by reactive()
Reactive State Helper Functions
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x)
// unwrapped is guaranteed to be number now
}
unRef()
Returns the inner value if the argument is a ref, otherwise return the argument itself. This is a sugar function for val = isRef(val) ? val.value : val
.
Reactive State Helper Functions
// returns existing refs as-is
toRef(existingRef)
// creates a readonly ref that calls the getter on .value access
toRef(() => props.foo)
// creates normal refs from non-function values
// equivalent to ref(1)
toRef(1)
toRef()
Can be used to normalize values / refs / getters into refs (3.3+).
const state = reactive({
foo: 1,
bar: 2
})
// a two-way ref that syncs with the original property
const fooRef = toRef(state, 'foo')
// mutating the ref updates the original
fooRef.value++
console.log(state.foo) // 2
// mutating the original also updates the ref
state.foo++
console.log(fooRef.value) // 3
toRef()
Can also be used to create a ref for a property on a source reactive object. The created ref is synced with its source property: mutating the source property will update the ref, and vice-versa.
Reactive State Helper Functions
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
toValue()
Normalizes values / refs / getters to values. This is similar to unref(), except that it also normalizes getters. If the argument is a getter, it will be invoked and its return value will be returned.
Reactive State Helper Functions
const state = shallowRef({ count: 1 })
// does NOT trigger change
state.value.count = 2
// does trigger change
state.value = { count: 2 }
shallowRef()
Makes value NOT deeply reactive
Reactive State Helper Functions
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
// The ref and the original property is "linked"
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
toRefs()
Converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object.
Questions?
ππΎββοΈ
Exercise 9
ποΈββοΈ ποΈ
Skip
Exercise 10
ποΈββοΈ ποΈ
Conclusion
πͺ Compositon APIΒ Benefits
- can organize by logical concern
- great for logic reuseΒ Β
- better performance
- no namespace collision and clear source of data (unlike mixins)
- many new libs written for CAPI
𧩠Good Fit For
- Re-using logic via composables
- Very long components for organization
- Everywhere if the whole team is on boardΒ
- At this point, recommended to use everywhere
- when TypeScript support is important
Recommended Video Courses
All courses included with your subscription!
Feel free to ask questions about topics in the courses and we usually respond in comments
Next Level Video Courses
All courses included with your subscription!
Feel free to ask questions about topics in the courses and we usually respond in comments
Other Workshops
Testing Fundamentals
and Vue Components
Learn the basics of unit testing and specifics for testing Vue.js components
State Management with Pinia
Learn how to use the officially recommended state management tool for Vue.js
Build Single Page Applications
Leverage the full power of the ecosystem to build a performant SPA (single page application).
TypeScript + Vue
Learn how to combine TypeScript with Vue.js for maintainable and scalable apps.
Other Great Resources
VueUse Source Code (great inspiration)
Other Great Resources
Vue Docs
(more composition functions mentioned there than in this workshop)
π© You will receive a Document with all the resources
π
πͺ
Help us improve
We'll be sending a survey
Q&A
πββοΈππΎββοΈ
Thank You!
π
Vue 3 Composition API (v3)
By Daniel Kelly
Vue 3 Composition API (v3)
- 1,284