Skip to main content

Composition

Composition API is another way to write components in Vue

Options API data, methods, computed, watchers lifecycle methods

Advantages

  • Easier to manage
  • group by feature (count data, increment, decrement all at same place)
  • re-usable logic blocks (like react cusom hooks)
  • Can be mixed along with options API

Data

with ref

import { ref } from 'vue
export default {
setup(){
const msg = ref('Hello world!')

return {
msg
}
}
}

ref function returns reactive mutable reference to an object

use .value to read of mutate the ref's value

value is automatically unpacked in template {{msg}}

const name = ref('World')
// set
name.value = 'Tony'
// read
const msg = ref(`Hello ${name.value}`)

with reactive

import { reactive } from 'vue
export default {
setup(){
const state = reactive({
name: 'Tony'
})

const msg = `Hello ${state.name}`

return {
msg
}
}
}

If state only has primitive values ref is easier to use as reactive only accepts objects

setup(){
const isLoggedInRef = ref(true)
const state = reactive({
isLoggedin: true
})
}

NOTE: for variables without ref or reactive vue does NOT react to their changes

NOTE: while returning from setup() if we return individual values of state vue does NOT react


<p>{{firstName}} {{lastName}}</p>

setup(){
const state = reactive({
firstName: 'Tony',
lastName: 'Stark'
})
// WRONG
return {
firstName : setup.firstName,
secondName: setup.secondName
}
}

to solve this vue provides toRef function

  • Can be used to create a ref for a property on a source reactive object. The ref can then be passed around, retaining the reactive connection to its source property.
const fooRef = toRef(state, 'foo')

fooRef.value++

import toRefs from 'vue'

<p>{{firstName}} {{lastName}}</p>

setup(){
return toRefs(state)
}

Methods

All methods inside setup() are automatically treated as methods

Return the method in setup() return object

setup(){
const count = ref(0)
function increment(){
count.value++;
}
return { count, increment }
}

NOTE: v-model works with both data() and setup()


Computed

import {computed} from 'vue'

setup(){
const fullName = computed(function(){
return `${firstName.value} ${lastName.value}`
})
return { fullName }
}

NOTE: computed properties also need .value to read or write in script


Watchers

with refs

import {watch} from 'vue'

setup(){
const count = ref('0)

watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue)
})

return { count }
}

watch multiple with array, new and old values are arrays as well

watch([firstName, lastName], (newValues, oldValues) => {
const [newFirstName, newLastName] = newValues
const [oldFirstName, oldLastName] = oldValues
console.log(newValues, oldValues)
}, {
// extra options in third argument object
immediate: true
})

with reactive

NOTE: the below function will have both newValue and oldValue same to fix this we need to pass a copy to the first argument of function

const state = reactive({
firstName: '',
secondName: '',
})

// will NOT work
watch(state, (newValue, oldValue) => {
console.log(newValue, oldValue)
}

// passing copy of state
watch(() => { return {...state} }, (newValue, oldValue) => {
console.log(newValue, oldValue)
}

// =============================================
// for single value
// will NOT work properly
watch(state.firstName, (newValue, oldValue) => {
console.log(newValue, oldValue)
}

// passing copy of state field value
watch(() => state.firstName, (newValue, oldValue) => {
console.log(newValue, oldValue)
}
}

NOTE: watch deep will not work with nested state object. We need to manually use some library to pass deep copied object as first argument

// lodash cloneDeep
const state = reactive({
firstName: '',
hero: {
superHeroName: ''
}
})

watch(() => _.cloneDeep(state), (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
deep: true
}
}

Provide/Inject

Can pass values as well as methods to children

// Parent
import { provide, ref } from 'vue'

setup(){
const msg = ref('Hello')
function changeMsg(){
msg.value = 'World';
}
provide('msg', msg)
provide('change', change)
return { msg, increment }
}

// Grand Child
import { inject } from 'vue'

setup(){
const msg = inject('msg', 'Default msg')
const increment = inject('increment')
return { msg, increment }
}

Lifecycle hooks

created, beforeCreated are not necessary as setup() is replacing them. Those should be written directly in setup() now

//mounted(){}
onMounted((){
console.log('Before mounted)
})

Template refs

<input ref="inputRef" />

import { ref } from 'vue'
setup(){
// use same name defined in ref attribute
const inputRef = ref(null)

onMounted(() => {
inputRef.value.focus()
})
}

Props

<p>{{fullName}}</p>

setup(props){
const fullName = computed(() => `${props.firstName} ${props.lastName}`)
return { fullName }
}

Component events

NOTE: context object has attrs, emit, slots

// Parent
<todo @deleteTodo="deleteTodo" />
setup(){
deleteTodo(todoId){
console.log(`Deleting ${todoId}`)
}
}

// Child
<button @click="deleteTodo">D</button>
setup(props, context){
function deleteTodo(){
context.emit('deleteTodo', props.todo.id)
}
return { deleteTodo }
}

Reusable Logic / Composables

Create /composables/counter.js

import {ref} from 'vue'
export default function useCounter(initialCount = 0, stepSize = 1){
const count = ref(initialCount)
function increment(){
count.value += stepSize
}
return { count, incrementCount }
}
// ClickCounter
<button @click="increment">Inc</button>

import 'useCounter' from './composables.useCounter'

setup(){
const { count, increment } = useCounter(0, 1)
return { count, increment }
}

// Hover
<button @mousover="increment">Inc</button>

import 'useCounter' from './composables.useCounter'

setup(){
const { count, increment } = useCounter(100, 10)
return { count, increment }
}