DEV Community

Roland Doda
Roland Doda

Posted on

Composition api or Options api?

Clarification 🧼

I would like to note that this article is not expressing hate or anything towards Vue.js.
I LOVE Vue and I sponsor the development of it πŸ’.
I just want to share my opinion, and I would love to hear your thoughts as well πŸ‘‚.

Options API is not Vue 2 πŸ™…

First, I see a lot of people referring to OptA (Options API) as it is Vue 2 and that using it means you also have to use mixins and whatnot. That's not true.
I don't see why we distinguish between OptA (Options API) and CompA (Composition API).
We can write CompA inside OptA, in the setup method:

import { useCounter } from './my-composables'

export default {
  setup() {
    const [count, increment] = useCounter()

    return { count, increment }
  },

  data: () => ({
    username: null
  })
}
Enter fullscreen mode Exit fullscreen mode

In fact, in the Composition API RFC we saw just that. The introduction of a new component option inside OptA called setup.
So Options API is not Vue 2. You can use CompA api using the setup method, which means you can use composables instead of mixins.

Composition api is powerful πŸ’ͺ

The first time I saw CompA I loved it! With it, we have more control about how we define reactivity, we can have reactivity outside of components and sharing code with composables is a great alternative to mixins.

The first time I tried the setup method along with OptA I thought to myself: "What a wonderful world" 🎢.

I replaced my mixins with composables, and I saw that CompA is really powerful and that it unlocks a lot of possibilities.
I could even extract and group code together that have the same logic, and I could even compose them together.
Composables look so clean, and they seem so simple compared to mixins.
Most importantly, you don't have to worry about the drawbacks of mixins.

No one complained about the Options API 🀨

It's crystal clear that CompA is a great addition to Vue 3. With it, you can dig deeper and manually control reactivity:

<script>
import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const nonReactive = 'I am not reactive'
    const person = reactive({
      name: 'John'
    })

    return { count, person }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Whereas with OptA reactivity works automatically:

<script>
export default {
  data: () => ({
    count: 0,
    person: {
      name: 'John'
    }
  })
}
</script>
Enter fullscreen mode Exit fullscreen mode

So for me, it was like:

Create components with Options API and in advanced cases use Composition API to better control reactivity.

But no! OptA syntax is no longer recommended by Vue team even though I never heard anyone complaining about it.

Options api for Todo apps? 😲

Vue docs say:

For primarily low-complexity scenarios use OptA.
For full applications, use CompA.

img.png

What does this mean? Use options api only for TODO apps? β“πŸ€”


Even though the setup method was introduced as a new component option, the docs recommend using it only if you have an existing OptA codebase. Because for new apps it's not recommended to use it:

Image description

So it's clear! OptA is not recommended and from my experience on reading the docs, it's discouraged.


Be careful! 😨

If you say that you like OptA more, some devs will attack you!
If you say that you don't like script setup syntax, some devs will respond with:

OptA is Legacy! You don't understand coding. You are a junior! Become a better dev just by using CompA.

DX of the Composition API πŸ›

Alright, since the Vue team & other devs push us to use CompA, we should drop OptA. Right? R i g h t ??
So let's see some CompA magic seen through my eyes.

Why ref and reactive ??

reactive has some limitations:

  1. We can't pass a nested property of a reactive variable to a function.
  2. We can't use destructuring.
const state = reactive({ count: 0 })

// the function receives a plain number and
// won't be able to track changes to state.count
callSomeFunction(state.count)

// count is a plain number that is disconnected
// from state.count.
let { count } = state
// does not affect original state
count++
Enter fullscreen mode Exit fullscreen mode

And thus, Vue provides us ref to avoid the limitations of reactive. Vue docs say that by using ref we can avoid the limitations of reactive and gives us the following snippet:

const obj = {
  foo: ref(1),
  bar: ref(2)
}

// the function receives a ref
// it needs to access the value via .value but it
// will retain the reactivity connection
callSomeFunction(obj.foo)

// still reactive
const { foo, bar } = obj
Enter fullscreen mode Exit fullscreen mode

So ref comes as a solution. But does it solve the limitations of reactive ? Why not use the same example we had with reactive but by using ref to have a better comparison ?

// const state = reactive({ count: 0 })
const state = ref({ count: 0 })

// callSomeFunction(state.count)
callSomeFunction(state.value)

// let { count } = state
let { count } = state.value
Enter fullscreen mode Exit fullscreen mode

Hmm, that doesn't work either. So ref has the same limitations.
If you pull out a property of that object you will get a non-reactive property.

ref is a smart trick by providing an object that has a value property. While you use the ref as is you are good. If you mess with .value though, you might end up with the limitations of reactive.

Usage in the template

You might wonder, how to display a ref variable in the template ? Should I access it via .value ?

The answer is no. Vue automatically "unwraps" the ref variable and displays the .value.

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  {{ count }}
  <button @click="count++">Increase counter</button>
</template>
Enter fullscreen mode Exit fullscreen mode

Updating the ref variable in the template can be done without using .value, as you can see from the example above where we directly do count++.

Ref unwrapping

So ref gets unwrapped when we use it in the template.
But not always:

  • Automatic unwrapping of ref applies only to top-level properties.
<script setup>
  import { ref } from 'vue'

  const object = { foo: ref(1) }
</script>

<template>
  {{ object.foo }} <!-- does NOT get unwrapped -->
</template>
Enter fullscreen mode Exit fullscreen mode
  • Automatic unwrapping of ref in reactive objects:
const count = ref(0)
const state = reactive({ count })

console.log(state.count) // no need for .value
Enter fullscreen mode Exit fullscreen mode
  • No unwrapping in reactive arrays or native collections like Map:
const books = reactive([ref('Vue 3 Guide')])
console.log(books[0].value) // need .value here

const map = reactive(new Map([['count', ref(0)]]))
console.log(map.get('count').value) // need .value here
Enter fullscreen mode Exit fullscreen mode

Ref syncing

Say that we have the 2 following variables:

import { ref, reactive } from 'vue'

const count = ref(0)
const state = reactive({ count })
Enter fullscreen mode Exit fullscreen mode

From the example above, here are some things that maybe don't work as you think they should work.

  • state.count can be accessed and mutated directly. No state.count.value needed.
  • If we update count variable then state.count will be updated too 😳.
  • If we update state.count then count will get updated too 😲.
  • So state.count and count.value are in sync. If we update one, the other will get updated too. But not always πŸ€”. If we re-assign to state.count another ref then the count will not be in sync anymore. 🀯

Whaaat 😳? Calm down, let's explain it.

So what happens, is that a ref inside a reactive object is getting unwrapped so no .value is available on the refs inside that object.
But remember that there is no unwrapping happening inside a reactive Array or a native collection type like Map or Set.

How about the sync part ? Why does that happen? And why does it stop only when assigning another ref ?

That's how it works. That can be powerful but with great power comes great responsibility.


A lot to keep in mind right? And we only touched on the ref and reactive APIs. But there is more:

Image description

So what? 🀷

So, with Composition API:

  • We have to decide what to use: ref or reactive? Believe me, there is no answer to that except that "it depends". Personally I mostly use ref.
  • You have to manually define and make sure you don't lose reactivity. As we learned above, ref and reactive limitations can easily lead to reactivity loss
  • You have to always keep in mind the limitations and the behaviors of how reactivity works. (unwrapping, syncing, .value, etc.)
  • Many APIs like toRef, toRefs, unref, isRef, etc.

It's true that CompA is really powerful, but I believe that the points above show us that the DX is not that great compared to OptA.

Conclusion (❁´◑`❁)

CompA is extremely powerful and has a lot of features, but it takes time to learn it and it can become difficult to debug especially if you don't use TypeScript.
Personally, I would say that the Development experience of OptA is much better than CompA for creating components.

You don't have to worry about reactivity loss or what apis to choose for reactivity and be careful with the limitations that come with them, but you focus on the component output.

Generally, I would say that using CompA for creating components has the following disadvantages:

  • Unorganized/verbose code.

I know that people show simple snippets where script setup looks cleaner, but the truth is that in real-world apps the code doesn't look that clean:

image.png

  • lots of lines of ungrouped code
  • difficult to visually analyze the code (refs, functions, comments, etc. in one place)
  • In order to make the code clean, devs usually add empty lines and comments (eg lifecycle hooks, watch etc) which is not ideal
  • it is not clean and intuitive and makes it hard to search for specific sections (eg where is a specific method)
  • enforce extraction. When a setup method becomes relatively big, since it has the above downsides, you feel the push to split it into composables. I've experienced that too many composables make it really difficult to debug and follow the flow of the code and also developers don't initially extract everything. Personally, I like building the component first, and then I do refactoring/cleanup code.

My thoughts πŸ’­

In the beginning of using Vue 3, I loved using OptA for creating components and CompA for things like sharing code, stores, etc. I thought that I had to use CompA for creating components only in advanced use cases just like I was using render-functions (only for advanced use cases).

And I believe that if the Vue team would push towards this way of using Vue, things would be much better.
But from what I see, the recommendation is to use script setup and stick with CompA only.
And this is exactly what I did. I switched to using only CompA.

But, sometimes I work on a Vue 2 project for a part-time job and when I use OptA I feel more productive, and I love the facts that I don't have to manually define reactivity and that I can fold/unfold section of the code, and I visually analyze the code better (where is the method, where is the computed, etc.).

Even though now I am confident using CompA, there are still cases where I fall into reactivity loss or forget about reactivity apis behaviors (e.g. unwrapping, syncing, .value, etc.).

Haven't tried script setup with Typescript extensively yet, but I am experimenting with it, and it definitely feels great. I would dare to say that script setup was made to work only with Typescript because without it, development experience is not that great.

Share your thoughts

All in all, CompA is great. OptA was great too.
Even though I loved OptA, I switched to CompA and probably is just a matter of time in order to get convinced that CompA is the way to go "for building full applications".

Or who knows? I might end up using OptA again despite the fact that docs discourage it. 😁

But what do you think? πŸ€”
Please share your thoughts. Let's discuss!

What do you prefer for creating components? Options or Composition API and why?

Discussion (4)

Collapse
fyodorio profile image
Fyodor

Very interesting thoughts and very reasonable indeed πŸ‘ I specially like the emotional way you bring it up with πŸ˜„πŸ‘

And I can totally relate. I don’t use Vue on day-by-day basis now (unfortunately), mostly occasionally, and mostly for prototypes, pet projects and teaching my son coding. And the reason I like Vue is that it’s very comprehensive and straightforward. It’s easy to explain web dev and programming with Vue even to a child. But all that is because of the options API. So deprecating it (if it’s on the map) is a really bad idea IMO.

Collapse
the_one profile image
Roland Doda Author

Hi Fyodor thank you for your comment. Glad to see that you liked the article.

I would say that they "soft-deprecating" the Options API. In the docs is mentioned that they don't plan deprecating it but if you read the docs throughout you will see that Options API is discouraged.

πŸ‘‰ In Vue 2, Options api was fine for full application but now in Vue 3 they say that options api is for low-complexity apps or without using a bundler.

πŸ‘‰ They also say that for new projects choose Composition api only and using it, it's better performance wise because you "shave-off" some kilobytes.

πŸ‘‰ Also, Code written in Composition API and script setup is also more efficient and minification-friendly than Options API equivalent.

So how can you win against the arguments to use CompA when you are in a team? You simply can't.

That's why I have switched totally to CompA.

If someone would ask me what is the best way to use Vue 3, I would say that it is Composition API with Typescript because I see that Vue team pushes us towards this way.

Collapse
lordsayur profile image
Omar

I totally agree with your regarding DX on using CompA. It is even worst when using CompA on Vue 2 project where we need to be aware all Vue 2 reactivity caveat. Things really get messy if i have array of big nested objects, and when doing v-for and want to process some of nested properties on an event by passing the nested property to event handler and trying to mutate the nested property. Normally i only use single ref on an array of nested objects. i never use nested reactive variable as i dont like the inconsistency auto unwrap behavior. the nested reactive is not fully documented in the document too. but i still like CompA especially in term of code organization. In my opinion, ref and reactive are quite messy. although i might handle the messiness, but it will be hard for any other junior dev on the same project

Collapse
the_one profile image
Roland Doda Author

Thanks for you comment Omar.