Introduction to

Vue.js 3 Composition Api

Composition API is the new way of defining components in Vue.js 3.

Vue.js Composition Api is the new, optional way of defining component’s logic, that will be officially introduced in version 3. Right now in Vue.js 2, we can still use the new Api by installing Composition Api plugin.

New Api is inspired by React.js Hooks. Usage and basic concepts of these Apis will look similar, yet their behaviour is different.

Component definition

Currently in Vue.js 2 components are based on creating an object whose properties are responsible for: state management, methods, lifecycle methods, props etc. By using Composition Api we will use mostly two properties: props for defining component’s props (already known from version 2) and setup method, that will contain a whole logic definition of our component. setup is a function that returns data that will be available in our component’s template.

export default {
  props: {
    // Props definition.
  },
  setup() {
    // Component's logic.

    // Returned data that will be used in a template.
    return {}
  }
}

Reading props

In Vue.js 2 props are accessed by referring to this of the component instance. Setup function from Composition API does not have own instance, yet props are passed as the first argument of the setup function.

export default {
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  setup(props) {
    // We can access props by refering to first argument of setup function.
    const userName = props.user.name;
    
    return { userName };
  }
}

Emitting events

Method for emitting events is defined on context object, that is passed as the second argument to setup. We use it by calling context.emit method and passing event name as a first argument and additional data as rest arguments.

export default {
  setup(props, context) {
    context.emit('counterUpdate', 10);
  }
}

// Vue.js 2 equivalent
this.$emit('counterUpdate', 10);

State management

ref()

First of the function for managing state is called ref, which we will be using to create a state that has a primitive type. Ref function returns an object with .value property, since primitive values in JavaScript have no reference, so it would not be possible to track changes if they were not objects. Example counter component logic using ref:

import { ref } from 'vue';

export default {
  setup() {
    const counter = ref(0);

    const increment = () => counter.value++;
  
    return { counter, increment };
  }
}

It is worth mentioning that there is no need to access ref values with .value property in Vue’s render template since it is being automatically unwrapped under the hood. We can simply use ref value as it would not be an object (which is counter in this case).

<template>
  <button @click="increment">
    // We are not accessing ref object
    // by ".value" property in a template.
    Counter is {{ counter }}
  </button>
</template>

reactive()

The second function is called reactive. This function accepts an object as a state value and it looks similarly to data method that is used in Vue.js 2 Api. This example has equivalent behaviour like ref’s one, but is using a reactive function:

import { reactive } from 'vue';

export default {
  setup() {
    const state = reative({
      counter: 0
    });

    const increment = () => state.counter++;
  
    return { state, increment };
  }
}

If you take a look, in comparison to ref function, there is no need anymore to access state value by .value property, since our state is already an object.

Reactive function has an additional feature, that when it receives ref as a value, it automatically unwraps it as it was primitive value. Same thing applies when we provide a computed value to a reactive, since computed returns ref object.

import { ref, reactive } from 'vue';

export default {
  setup() {
    const counter = ref(0);

    const state = reative({
      // We have provided `counter` (ref object) to our state
      counter
    });

    // We can access `counter` property without need of additional `.value` access,
    // since `counter` object has been automatically unwrapped.
    const increment = () => state.counter++;
  
    return { state, increment };
  }
}

computed()

A computed function has the same behaviour and purpose as in Vue 2, yet it returns an object with .value property as a result - it simply returns ref object. Computed function accepts a callback as an argument that will be memoized until one of its dependencies (observed values) changes. In this particular case, our observed value is counter.

import { ref, computed } from 'vue';

export default {
  setup() {
    const counter = ref(0);

    const doubledCounter = computed(() => counter.value * 2);
  
    return { counter, doubledCounter };
  }
}

Side effects

watch()

watch is a function that similarly to computed will run if one of it’s dependencies changes, yet it does not return any value, instead it runs side effects like console log, or fetches new data when an observed state has changed.

import { ref, watch } from 'vue';

export default {
  setup() {
    const counter = ref(0);

    // Everytime counter changes, it's value will be displayed in a console.
    watch(() => console.log(counter.value));

    return { counter };
  }
}

Lifecycle hooks

Most of the lifecycle behaviour remains the same as in Vue 2, whereas lifecycle method names start with on prefix. Each method accepts a callback that will be called when specific lifecycle occurs. Example usage of onMounted lifecycle method:

import { onMounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Component has mounted');
    });

    return {};
  }
}

Due to implementation reasons, in Composition Api, beforeCreate and created lifecycles have been moved to setup function and this is the place where they do occur.

AL.
Github