DEV Community

Cover image for What I Have Learned So Far about 'Vue-Composition-API'
Arif Dinulsyah Putra
Arif Dinulsyah Putra

Posted on

What I Have Learned So Far about 'Vue-Composition-API'

"What I Have Learned So Far" Edition also known as sequence of series that i am personally developing for my writing routine. Originated from certain topic or reading material lately that I enjoy, cheers 🥂.

After following some news about vue lately, My eyes 👀 caught by the new interesting thing that will come and which is a major update from Vue 2 to Vue 3 expected release in mid to end term this year. One of the significant transformations is in the new alternative code structure from the options API (previous way) and then the composition API (new optional way).

Options API Composition API
- Intuitive and easy to get started with - Provides more flexible code organization & logic reuse capabilities
- Scalability issues in large applications - Can be use alongside options API

Then here are some reasons and a comparison between composition and options according to Evan You (The creator of Vue JS) and the image below shows the different code structures of options and composition API.

Alt Text

original Source : vue-composition-api-rfc.netlify.com (vue-composition-documentation)

1. Getting Started

I have used a simple implementation called 📝 refrigerator-note 📝 mainly using @vue/composition-api and vue-cli for this topic and here is the Github repo. You can clone it and run several commands like this on your local machine:

  • yarn install / npm install (install the dependency)
  • yarn serve / npm run serve (running the application)

The vue2 code structure is placed in the master branch and composition in the api-composition branch and here is a list of existing users and passwords if you want to try accessing the online demo/local machine lastly don't forget to leave a star for the repo if you like it 🌟

// src/main.js
import Vue from 'vue'
import App from './App.vue'
import VueCompositionApi from '@vue/composition-api'
import Miligram from 'milligram'
import Vmodal from 'vue-js-modal'

Vue.config.productionTip = false
Vue.use(VueCompositionApi)
Vue.use(Miligram)
Vue.use(Vmodal)

new Vue({
  render: h => h(App),
}).$mount('#app')

2. Setup

The composition API requires a setup initialization that will return an object that wraps state, methods, computed, watch , etc to expose this variables/functions to the templating Vue. And also note that this statement cannot be used in the setup.

// src/components/RefrigeratorLoginForm.vue

// Vue 2 Structure

<template>
  ....
</template>

<script>
export default {
  name: 'RefrigeratorLoginForm',
  props: {
     ...
  },
  data(){
    return{
      ...
    }
  },
  computed:{
    isDisable:function(){
      ...
    }
  },
  methods:{
    loginForm(){
      ...
    }
  }
}
</script>

<style scoped>
</style>

// Vue Composition Structure

<template>
  ....
</template>

<script>
export default {
  name: 'RefrigeratorLoginForm',
  props: {
    loginMethod: {
      ...
    }
  },
  setup(props){
    ....

    return{
      username,
      ....
    }
  }
}
</script>

<style scoped>
</style>

3. Local/Component State

In the composition API, there are two ways to explicitly initiate a local state with ref or reactive that observe for each state change, the ref takes parameters without being wrapped into an object while reactive uses the object data type and assign a specific key. These two are optional but there are some differences in where to access the ref state in the setup function with the variablename.value and the variablename in the Vue template. Whereas reactive is also accessed by the assign key name in the setup and the template so it kind of makes senses in my opinion.


// src/components/RefrigeratorNoteForm.vue

// Vue 2 Structure

<template>
  <div>
  <input class="button-clear button-small float-right" type="submit" value="Log Out" @click="logOutNow">
  <div class="component">
    <input class="button button-clear" type="submit" value="---> See List Of Note <---" @click="toListNote">
    <h3 class="center">Send Your Note</h3>
    <form @submit.prevent="addNote">
    <fieldset>
      <label for="nameField">Send To</label>
      <select id="availableUser" v-model="selectedUser">
        <option value="" selected>Select Available User</option>
        <option :value="user" :key="user" v-for="user in allUsers">{{ user }} </option>
      </select>
      <label for="nameField">Note</label>
      <input type="text" :placeholder="placeholder" id="nameField" v-model="inputNote">
      <input class="button-black float-right" type="submit" value="Write" :disabled="isDisable">
    </fieldset>
    </form>
  </div>
  </div>
</template>

<script>
import { Users } from '../users';
export default {
  data(){
    return{
      placeholder:'',
      allUsers:[],
      inputNote:'',
      minNote:10,
      username:'',
      selectedUser:''
    }
  },
  props:{
    toSection:{
      type:Function,
      required:true
    },
    sendNote:{
      type:Function,
      required:true
    },
    logoutMethod:{
      type:Function,
      required:true
    }
  },
  computed:{
    isDisable:function(){
      return !(this.inputNote.length > this.minNote && this.selectedUser !== '');
    }
  },
  methods:{
    toListNote(){
      this.toSection({section:'on-note'})
    },
    addNote(){
      this.sendNote({
        from:this.username,
        sendTo:this.selectedUser, 
        note:this.inputNote
      })
      this.selectedUser = ''
      this.inputNote = ''
    },
    logOutNow(){
      this.logoutMethod()
    }
  },
  mounted(){
    this.username = Users[localStorage.getItem('username')]['name']
    this.placeholder = `life-and-death Note From ${this.username}`
    this.allUsers = Object.keys(Users)
      .filter(user => user !== localStorage.getItem('username'))
      .map(user => Users[user]['name'])
  }
}
</script>

<style scoped>
...
</style>


// Vue Composition Structure

<template>
  <div>
  <input class="button-clear button-small float-right" type="submit" value="Log Out" @click="logOutNow">
  <div class="component">
    <input class="button button-clear" type="submit" value="---> See List Of Note <---" @click="toListNote">
    <h3 class="center">Send Your Note</h3>
    <form @submit.prevent="addNote">
    <fieldset>
      <label for="nameField">Send To</label>
      <select id="availableUser" v-model="selectedUser">
        <option value="" selected>Select Available User</option>
        <option :value="user" :key="user" v-for="user in allUsers.data">{{ user }} </option>
      </select>
      <label for="nameField">Note</label>
      <input type="text" :placeholder="placeholder" id="nameField" v-model="inputNote">
      <input class="button-black float-right" type="submit" value="Write" :disabled="isDisable">
    </fieldset>
    </form>
  </div>
  </div>
</template>

<script>
import { Users } from '../users';
import { reactive, ref, computed, onMounted } from '@vue/composition-api'
export default {
  name: 'RefrigeratorNoteForm',
  props:{
    toSection:{
      type:Function,
      required:true
    },
    sendNote:{
      type:Function,
      required:true
    },
    logoutMethod:{
      type:Function,
      required:true
    }
  },
  setup(props){
    let placeholder = ref('')
    let allUsers = reactive({
      data:[]
    })
    let selectedUser = ref('')
    let inputNote = ref('')
    let minNote = ref(10)
    const addNote = () => {
      props.sendNote({
        from:username.value,
        sendTo:selectedUser.value, 
        note:inputNote.value
      })
      selectedUser.value = ''
      inputNote.value = ''
    }
    let username = ref()
    const logOutNow = () => {
      props.logoutMethod()
    }
    const isDisable = computed(() => {
      return !(inputNote.value.length > minNote.value && selectedUser.value !== '');
    })
    const toListNote = () => {
      props.toSection({section:'on-note'})
    }
    onMounted(() => {
      username.value = Users[localStorage.getItem('username')]['name']
      placeholder.value = `life-and-death Note From ${username.value}`
      allUsers.data = Object.keys(Users)
        .filter(user => user !== localStorage.getItem('username'))
        .map(user => Users[user]['name'])
    })
    return {
      placeholder,
      allUsers,
      inputNote,
      selectedUser,
      isDisable,
      addNote,
      toListNote,
      logOutNow
    }
  }
}
</script>

<style scoped>
...
</style>

4. Methods, Computed, Props, Watch, LifeCycle Hooks

The Methods written as a usual function in Javascript

While Computed calling Computed from Vue-Composition then passing a function as parameters stored the expected result in a variable that implicitly has been observed by Vue so that to access it we need to use variablename.value.

Props, Same as a previous structure in Vue 2 but props are received from the first parameter setup function.

Importing OnMounted(Life Cycle Hooks) and Watch from composition-API then like Vue 2 for onMounted are called when the template has been rendered and we can access data, components, global objects, etc. While Watch will be called if there is a change in the watch state and then perform a logical process by passing again a function on OnMounted or Watch.

// src/App.vue

// Vue 2 Structure

<template>

</template>

<script>
import RefrigeratorLoginForm from './components/RefrigeratorLoginForm.vue'
import RefrigeratorNoteForm from './components/RefrigeratorNoteForm.vue'
import ListNoteTable from './components/ListNoteTable'
import { Users } from './users'
export default {
  name: 'app',
  components: {
    RefrigeratorLoginForm,
    RefrigeratorNoteForm,
    ListNoteTable
  },
  data(){
    return{
      noteList:[],
      onPage: null,
      logoutState:false
    }
  },
  methods:{
    loginRefrigerator({username, password}){
      (Users[username] && Users[username]['password'] === password) 
        ? this.setLogin(username) 
        : this.modalShow()
    },
    addNoteToList({from,sendTo,note}){
      this.noteList.push({
        from,
        sendTo,
        note
      })
    },
    deleteNote(index){
      this.noteList.splice(index,1)
      this.$modal.show('delete')
    },
    setLogin(username){
      this.onPage = 'on-login';
      this.username = username;
      localStorage.setItem('section',this.onPage)
      localStorage.setItem('username',this.username)
    },
    changeSection({section}){
      this.onPage = section
      localStorage.setItem('section',section)
    },
    modalShow(){
      this.$modal.show('error-login');
    },
    logoutRefrigerator(){
        this.logoutState = true
    },
    modalLogout(){
      this.$modal.show('logout');
    },
  },
  watch:{
    logoutState:function(newState){
      if(newState){
        this.onPage = 'on-no-login'
        localStorage.clear()
        this.modalLogout()
        this.logoutState = false
      }
    }
  },
  created(){
    (Users[localStorage.getItem('username')]) 
      ? (this.onPage = localStorage.getItem('section')) 
      : this.onPage = 'on-no-login'
  }
}
</script>


// Vue Composition Structure

<template>
  ....
</template>

<script>
import RefrigeratorLoginForm from './components/RefrigeratorLoginForm.vue'
import RefrigeratorNoteForm from './components/RefrigeratorNoteForm.vue'
import ListNoteTable from './components/ListNoteTable'
import { reactive, ref, onMounted, watch } from '@vue/composition-api'
import { Users } from './users'
export default {
  name: 'app',
  components: {
    RefrigeratorLoginForm,
    RefrigeratorNoteForm,
    ListNoteTable
  },
   setup(props,context){
    let noteList = reactive({
      data:[]
    })
    const addNoteToList = ({from,sendTo,note}) => {
      noteList.data.push({
        from,
        sendTo,
        note
      })
    }
    const deleteNote = (index) => {
      noteList.data.splice(index,1)
      context.root.$modal.show('delete')
    }
    let onPage = ref(null)
    const changeSection = ({section}) => {
      onPage.value = section
      localStorage.setItem('section',section)
    }
    let username = ref('')
    const setLogin = (user_name) => {
      onPage.value = 'on-login';
      username.value = user_name;
      localStorage.setItem('section',onPage.value)
      localStorage.setItem('username',username.value)
    }
    const loginRefrigerator = ({username, password}) => {
        (Users[username] && Users[username]['password'] === password) 
        ? setLogin(username) 
        : modalShow()
    }
    let logoutState = ref(false)
    const logoutRefrigerator = () => {
        logoutState.value = true
    }
    const modalLogout = () => {
      context.root.$modal.show('logout')
    }
    watch(() => {
      if(logoutState.value){
        onPage.value = 'on-no-login'
        localStorage.clear()
        modalLogout()
        logoutState.value = false
      }
    })
    const modalShow = () => {
      context.root.$modal.show('error-login')
      //this.$modal.show('error-login')
    }
    onMounted(() => {
      (Users[localStorage.getItem('username')]) 
      ? (onPage.value = localStorage.getItem('section')) 
      : onPage.value = 'on-no-login'
    })
    return{
      noteList,
      onPage,
      loginRefrigerator,
      addNoteToList,
      deleteNote,
      changeSection,
      logoutRefrigerator
    }
  }
}
</script>

5. Structure and Use Composable

Vue composition supports the code structure for better readability and ease of maintenance in my opinion, we can also use the composable function to group domains separately in files and then call them or combine them with other composable functions.

// src/cmp-fun/ListNoteTable.js

export default function(props){
    const toAddNote = () => {
        props.toSection({section:'on-login'})
    }

    const deleteList = (index) => {
        props.deleteNote(index)
    }

    return {
        toAddNote,
        deleteList
    }
}
// src/components/ListNoteTable.vue
<template>
  <div class="component">
      <div class="float-right">
        <input class="button button-clear" type="submit" value="-> Back To Write Your Note" @click="toAddNote">
      </div>
       <h3 class="center">List Of Notes</h3>
        <table>
            <thead>
                <tr>
                    <th>From</th>
                    <th>Send To</th>
                    <th>Note</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                <tr :key="index" v-for="(note,index) in listNote">
                    <td>{{note.from}}</td>
                    <td>{{note.sendTo}}</td>
                    <td>{{note.note}}</td>
                    <td>
                        <button class="button button-outline button-small" @click="deleteList(index)">Delete</button>
                    </td>
                </tr>        
            </tbody>
        </table>
  </div>
</template>

<script>
import useListNote from '../cmp-fun/ListNoteTable'
export default {
    props:{
        toSection:{
            type:Function,
            required:true
        },
        listNote:{
            type:Array,
            required:true
        },
        deleteNote:{
            type:Function,
            required:true
        }
    },
    setup(props){
       return useListNote(props)
    }
}
</script>

<style scoped>
...
</style>

6. Accessing Plugin through Context

Due to not being able to access this statement on the Vue composition, one way to access global object or plugins that we usually use like this.$modal is with the params context in the second parameter and then in the setup function and call it like so context.root.$modal.

// src/App.vue
<template>
 ...
</template>

<script>
...

export default {
  ....
  setup(props,context){
    ....

    const modalLogout = () => {
      context.root.$modal.show('logout')
    }

    return{
      ...
    }
  }
}
</script>

Vue Composition in Vue 3 later remains a choice that is not mandatory but also intended to tackle the problem for large scale application scalability and maintainability with long maintenance and life span.Another interesting thing denoted that the composition/options API can be used together according to the desired requirements. So are you impatient to anticipate the release of Vue 3 or other great things coming from Vue ?

Top comments (0)