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

@danielkelly_io

@danielkellyio

Alabama, USA

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
  • 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? 🧐

  1. Too Easy
  2. Too Hard
  3. 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 πŸ”₯

Grab Some SWAG πŸ€—

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,004