vueschool.io
@vueschool_io
Vue 3 and the Composition API
Vue 2
Vue is Options based
Vue is Options based
new Vue({
data: {
loading: false,
count: 0,
user: {}
},
computed: {
double () { return this.count * 2 },
fullname () {/* ... */}
},
methods: {
increment () { this.count++ },
fetchUser () {/* ... */}
}
})
Vue 3
Vue 3
𧱠Composition API
𧱠Composition API
π advanced feature
π options API is not being deprecated
β addition to the current API
𧱠Composition API
a set of lower level APIs
use Vue features outside of Vue components
𧱠Composition API
import {ref, computed, watch} from 'vue'
LIVE EXAMPLE
New Feature in
Vue 3
the setup option
𧱠Composition API
INSIDE
Vue Components
using setup
new Vue({
beforeCreate () {
},
setup () {
},
created () {
}
})
the setup option
looks like
Lifecycle hooks
Is More Powerful Than
the setupΒ option
the setup option
<template>
<button @click="increaseCount">count {{count}} | double {{double}}</button>
</template>
<script>
import {computed, ref} from 'vue'
export default {
setup () {
const count = ref(0)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {increaseCount, count, double} // render context - unwrapped
},
mounted () {
this.count = 40
this.increaseCount()
}
}
</script>
the setup option
ButtonCounter.vue
Questions?
Exercise 1
Daniel Kelly
Teacher @ Vue School
Full Stack developer (10 years)
Husband and Father
setup(){
//...
return {
characters,
loadingState,
fetchAllCharacters
};
}
Gets Really Annoying
<script setup>
compile-time syntactic sugar for using Composition API inside Single-File Components (SFCs)
<script setup>
import { ref } from "vue";
const characters = ref([]);
const loadingState = ref(null);
function fetchAllCharacters() {
//...
}
</script>
<script setup>
//...
</script>
<template>
//...
</template>
Convention = Script setup ABOVE template
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>
Import Components to Register them
<script setup>
// enables v-focus in templates
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
Define directives by prefixing with v
Questions?
Exercise 2
Why Composition API
β»οΈ Logic Reuse
π Code Organization
π Improved TypeScript Support
π Code Organisation
Organise Code By Options
import axios from 'axios'
import orderBy from 'lodash.orderby'
export default {
data () {
return {
characters: [],
loadingState: null,
orderKey: 'id'
}
},
computed: {
charactersOrdered() {
return orderBy(this.characters, this.orderKey)
}
},
methods: {
fetchAllCharacters () {
this.loadingState = 'loading'
axios.get('https://rickandmortyapi.com/api/character')
.then(response => {
setTimeout(() => {
this.loadingState = 'success'
this.characters = response.data.results
}, 1000)
})
},
setOrderKey(key) {
this.orderKey = key
}
},
created () {
this.fetchAllCharacters()
}
}
Fetch Resource Feature
Order Array Feature
<script setup>
import axios from 'axios'
import orderBy from 'lodash/orderby'
import {computed, ref} from 'vue'
const characters = ref([])
const loadingState = ref(null)
const fetchAllCharacters = () => {
loadingState.value = 'loading'
return axios.get('https://rickandmortyapi.com/api/character')
.then(response => {
loadingState.value = 'success'
characters.value = response.data.results
})
}
fetchAllCharacters()
const orderKey = ref('id')
const charactersOrdered = computed(() => {
return orderBy(characters.value, orderKey.value)
})
const setOrderKey = (key) => {
orderKey.value = key
}
</script>
Organise Code By Feature
Fetch Resource Feature
Order Array Feature
Code Organisation
By Options VS By Feature
Questions?
Exercise 3
β»οΈ Logic Reuse
Logic Reuse
<template>
<button @click="increaseCount">count {{count}} | double {{double}}</button>
</template>
<script>
import {computed, ref} from 'vue'
export default {
setup () {
const count = ref(0)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {increaseCount, count, double} // render context - unwrapped
},
mounted () {
this.count = 40
this.increaseCount()
}
}
</script>
Button Component From Earlier
<script>
import {computed, ref} from 'vue'
const useCounter = () => {
const count = ref(0)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {count, double, increaseCount}
}
export default {
setup () {
const {increaseCount, count, double} = useCounter()
return {increaseCount, count, double} // render context
}
}
</script>
Logic Reuse
<script>
import {computed, ref} from 'vue'
const useCounter = (initial = 0) => {
const count = ref(initial)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {count, double, increaseCount}
}
export default {
setup () {
const {increaseCount, count, double} = useCounter(40)
return {increaseCount, count, double}
}
}
</script>
Logic Reuse
// src/composables/useCounter.js
import {computed, ref} from 'vue'
export const useCounter = (initial = 0) => {
const count = ref(initial)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {count, double, increaseCount}
}
Logic Reuse
Can extract to another file
// src/composables/useCounter.js
import {computed, ref} from 'vue'
export const useCounter = (initial = 0) => {
const count = ref(initial)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {count, double, increaseCount}
}
Logic Reuse
Can extract to another file
// CounterButton.vue
<script>
import {useCounter} from "@/composables/useCounter"
export default {
setup () {
const {increaseCount, count, double} = useCounter(40)
return {increaseCount, count, double}
}
}
</script>
// src/composables/useCounter.js
import {computed, ref} from 'vue'
export const useCounter = (initial = 0) => {
const count = ref(initial)
const double = computed(() => count.value * 2)
const increaseCount = () => {
count.value++
}
return {count, double, increaseCount}
}
Logic Reuse
Can extract to another file
// CounterButton.vue
<script setup>
import {useCounter} from "@/composables/useCounter"
const {increaseCount, count, double} = useCounter(40)
</script>
Logic Reuse
Fetch Data
// /src/composables/useFetchResource.js
import {ref} from 'vue'
import axios from 'axios'
export default function useFetchResource (url) {
const data = ref([])
const loadingState = ref(null)
const fetchResource = () => {
loadingState.value = 'loading'
return axios.get(url)
.then(response => {
loadingState.value = 'success'
data.value = response.data.results
})
}
return {data, loadingState, fetchResource}
}
Logic Reuse
Fetch Data
The Component
<script setup>
// ...
const characters = ref([]);
const loadingState = ref(null);
function fetchAllCharacters() {
loadingState.value = "loading";
axios.get("https://rickandmortyapi.com/api/character").then((response) => {
setTimeout(() => {
loadingState.value = "success";
characters.value = response.data.results;
}, 1000);
});
}
fetchAllCharacters();
</script>
CharacterCards.vue
The Component
<script setup>
import { useFetchResource } from "@/composables/useFetchResource";
// ...
const {
data: characters,
fetchResource: fetchAllCharacters,
loadingState,
} = useFetchResource("https://rickandmortyapi.com/api/character");
fetchAllCharacters();
</script>
CharacterCards.vue
<template></template>
<script>
import useMousePosition from '@/composables/useMousePosition'
export default {
setup () {
const {x, y} = useMousePosition()
// ...
}
}
</script>
π Logic Reuse
π Logic Reuse
Logic Reuse Alternatives
- Mixins
- Higher Order Components
- Renderless Components
Respective Drawbacks when compared with the Composition API
Questions?
Exercises 4-5
LifeCycle Hooks
LifeCycle Hooks
// MyComponent.vue
<script setup>
import { onMounted, onUnmounted } from "vue";
onMounted(()=>{
// do things
})
</script>
LifeCycle Hooks
// useMyComposable.js
import { onMounted, onUnmounted } from "vue";
export function useMyComposable(){
onMounted(()=>{
// do things
})
}
Questions?
Exercise 6
Props In the Composition API
<script>
export default {
props: ['foo'],
setup(props) {
// setup() receives props as the first argument.
console.log(props.foo)
}
}
</script>
Props In the Composition API
<script setup>
const props = defineProps({
foo: String,
});
console.log(props.foo)
</script>
Events In the Composition API
<script>
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
</script>
Events In the Composition API
<script setup>
const emit = defineEmits([
'inFocus',
'submit'
])
emit('submit')
</script>
Template Refs In the Composition API
<script>
export default{
onMounted(){
this.$refs.input.focus()
}
}
</script>
<template>
<input ref="input" />
</template>
Template Refs In the Composition API
<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>
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 vs Reactive
// β
ref works
const framwork = ref('Vue')
const isAwesome = ref(true)
const created = ref(2014)
Ref Works on Primitives
Reactive does not
// β 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']
Can Re-assign whole ref values but not reactive
// β 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)
Ref requires .value reactive does not
// reactive
const framework = reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
})
console.log(framework.name)
Which to use? π€
- Sometimes you have no choice
- Must use refs for primitives
- Must use ref when replacing whole value
- Must use refs for primitives
- Otherwise it's personal preference
- Many choose to use ref for everything
// ref
const {name} = toRefs(reactive({
name: 'Vue',
author:'Evan You',
tags: ['javascript', 'vue']
}))
console.log(name.value)
Β Can convert reactive object to refsΒ
Can convert reactive object to refs
Questions?
Exercises 7-8
Conclusion
Composition API Benefits
- extremely flexible
- clear source of properties
- performance
- no namespace collision
- new libs written for CAPI
Consider π
- no need to rewrite your Vue 2 app
- no need to use it in a simple component
- keep the junior devs in mind
(own opinion)
Good Fit For
- component code grows too long
- team that work on the same (big) components
- reuse component options without using mixins
- when TypeScript support is important
- The whole team agrees to do CAPI everywhere
(own opinion)
How was the pace of workshop? π§
How was the pace of workshop? π§
- Too Easy
- Too Hard
- Just Fine
Courses to Dig Deeper into the Composition API
Courses to learn further Vue
Other Great Courses too!
Vue Corporate Training
NEW
πΊ
Vue Video
Courses
π¨βπ«
Live Workshops
π¬
Bi-weekly Support
Sessions
π§βπ§
Database for hiring
Vue devs
#1 Learning Resource for Vue.js
Our goal
800+ Video Lessons
140000 users
Alex Kyriakidis
Daniel Kelly
Debbie O'Brien
Chris Fritz
Maria Lamardo
Roman Kuba
Anthony Fu
Filip Rakowski
Alexander Lichter
Lydia Hallie
Workshops
Interested? Talk to our team at the conference or send us an email
π team@vueschool.io
How to find us
vueschool.io
@hootlex
@danielkelly_io
team@vueschool.io
πΊ
Workshops
Interested? Talk to our team at the conference or send us an email
π team@vueschool.io
π₯ Vue.js Berlin Special Offer π₯
Thank You π
Vue 3 and the Composition API (v2)
By Daniel Kelly
Vue 3 and the Composition API (v2)
Vue 3 and the Composition API
- 1,389