@vueschool_io
vueschool.io
Essential Composition API syntax and functions
Setup Exercise Project Locally
All exercises can be completed on StackBlitz as well
β° 10 mins
What do you think?
Answer in the chat
new Vue({
data(){
return {
loading: false,
count: 0,
user: {}
}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
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'
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
ποΈββοΈ ποΈ
So far, we've used the setup option without a build tool but the experience isn't great
<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>
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>
compile-time syntactic sugar for using Composition API
inside Single-File Components (SFCs)
<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>
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
<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
<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
<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
<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
<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
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
Remove all the returns
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
<script setup>
const loading = ref(true);
const products = ref([]);
const numberOfProducts = computed(() => products.value.length);
async function fetchProducts() {
//...
}
</script>
<script setup>
//...
</script>
<template>
//...
</template>
<template>
//...
</template>
<script>
export default{}
</script>
β
β
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
<script setup>
import HelloWorld from "@/components/HelloWorld.vue";
</script>
β
β
<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>
<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
<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
<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>
β
β
ποΈββοΈ ποΈ
Code Organization
Logic Reuse
Improved
TypeScript Support
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
ποΈββοΈ ποΈ
Skip
<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
<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)
<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
// 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>
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 };
};
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>
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>
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 };
};
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 };
};
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>
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>
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 };
};
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>
Vue 2 did provide alternatives for logic re-use but they all had their drawbacks
Vue 2 did provide alternatives for logic re-use by they all had their drawbacks
ποΈββοΈ ποΈ
Off the Shelf Composables
npm i @vueuse/core
<script setup>
import {useMouse} from "@vueuse/core"
const { x, y } = useMouse()
</script>
<template>
<div>pos: {{x}}, {{y}}</div>
</template>
<script setup>
import { useFetch } from '@vueuse/core'
const { isFetching, error, data } = await useFetch(url)
</script>
<script setup>
import { useLocalStorage } from '@vueuse/core';
const framework = useLocalStorage('framework', null);
</script>
<template>
<input type="text" v-model="framework" />
</template>
Let's visit the website and see what else is available
ποΈββοΈ ποΈ
// MyComponent.vue
<script setup>
import { onMounted, onUnmounted } from "vue";
onMounted(()=>{
// do things
})
</script>
Use in components
// 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
β
onCreated function does not exist
// 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
// 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
// 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
ποΈββοΈ ποΈ
Skip
<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
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)
// β
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
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)
// β
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
// β
will work
const framework = toRefs(reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
}))
const { name } = framework
// @/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>
Ref or Reactive
Ref or Reactive
Vue Docs recommend
to use ref everywhere
let foo = ref("");
if (isRef(foo)) {
// true
}
isRef()
Checks if a value is a ref object.
let foo = reactive({...})
if (isReactive(foo)) {
// true
}
isReactive()
Checks if an object is a proxy created by reactive()
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
.
// 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.
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.
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
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.
ποΈββοΈ ποΈ
Skip
ποΈββοΈ ποΈ
All courses included with your subscription!
Feel free to ask questions about topics in the courses and we usually respond in comments
All courses included with your subscription!
Feel free to ask questions about topics in the courses and we usually respond in comments
Testing Fundamentals
and Vue Components
Learn the basics of unit testing and specifics for testing Vue.js components
Learn how to use the officially recommended state management tool for Vue.js
Leverage the full power of the ecosystem to build a performant SPA (single page application).
Learn how to combine TypeScript with Vue.js for maintainable and scalable apps.
VueUse Source Code (great inspiration)
Vue Docs
(more composition functions mentioned there than in this workshop)
πͺ
Help us improve
We'll be sending a survey
Q&A
πββοΈππΎββοΈ