The Options API
With the current Vue 2.x way of building components, we're separating it by the option, not feature. What this means is that for example, a single toggling state for showTitle
will need to have a reactive data in data
and a method
to toggle the state. For a small simple component, this won't be a problem. But as the component grows, more functionality will be added, thus making reading the whole feature more difficult. An example can be seen below.
<template>
<div>
<h2>Options API</h2>
<p>{{ total }}</p>
<p>
Data:
<span
v-for="(n, idx) in apiRes"
:key="idx">
{{ n }}
</span>
</p>
<input type="text" v-model="searchInputText"/>
<p>Occurence: {{ results }}</p>
<button
@click="toggleAddForm">
Add New Entry
</button>
<div v-if="showAddForm">
<input type="text" v-model="newInputText"/>
<button
@click="add">
Add
</button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
// #1 Search Form
searchInputText: '',
apiRes: ['Google', 'Amazon', 'Facebook', 'Uber', 'Netflix', 'Google', 'Twitter', 'Amazon'],
results: 0,
// #2 Input Form
newInputText: '',
showAddForm: false,
}
},
computed: {
// #1 Search Form
total () {
return `Total data: ${this.apiRes.length}`
}
},
watch: {
// #1 Search Form
searchInputText (value) {
this.results = this.apiRes.filter(itm => itm === value).length
},
},
methods: {
// #2 Input Form
toggleAddForm () {
this.showAddForm = !this.showAddForm
},
// #2 Input Form
add () {
if (this.newInputText) {
this.apiRes.push(this.newInputText)
}
}
},
}
</script>
Now that might still be readable to some extent, but as you can see we started to get different concerns of functionality split by data
and methods
. This will continue to get more complex when the component grows, let alone if we need to add computed
or lifecycle hooks.
Now on Vue 2.x, we have mixins
that can address this concern. Mixins can be used for functionality purposes, making related data end methods in one file, then importing that file wherever it needed. For me, this still looks relevant and is the way to go for most cases (this post doesn't imply we should make it obsolete either). The downside on this, however, is that sometimes we need to know what happens in it, to decide if we can use it as-is, or we need to adjust it for our needs. We'll need to open the file, read again and when finished, check other places that use it making sure none of them broke. Other things are that there is a great potential that mixins can have similar names, which will make various issues.
Composition API
The upcoming Composition API is the solution Vue 3 introduces to resolve the above issues. At the moment of writing (May 2020) it is still in beta, but we can already try it. It can make related data, methods, and functions scoped in one place instead of spread all over the file. We can use it in existing Vue 2.x projects by installing an additional npm package.
npm i --save @vue/composition-api
Afterward, we will need to import the package in our app.js
file as we can see below.
import Vue from "vue";
import VueCompositonApi from "@vue/composition-api";
...
Vue.use(VueCompositonApi);
If you are starting from scratch with Vue 3.0 however, we can directly import everything in a .vue file from vue
. For example:
import { refs, reactive } from "vue";
We can now access the setup()
function inside our .vue
file. This function will run on component initialization, acts as a replacement to beforeCreate
and created
lifecycle hooks. Now let's see how we can use some of this enhancement compared to existing Options API in the previous Vue version.
Data Reactivity
Reactivity is the main advantage when using Front End Javascript framework like Vue. In Vue 2.5 we are defining our data in data ()
function or data
object. In Composition API, we need to import ref
or reactive
from @vue/composition-api
to achieve the same functionality.
...
<script>
import { ref, reactive } from "@vue/composition-api"
export default {
name: 'ComposedComponent',
setup () {
// Using ref, for simple values
const searchInputText = ref('')
const results = ref(0)
// Using reactive, for object values
const optData = reactive({
displayTitle: true
})
// Accessing
const toggleDisplay = () => {
optData.displayTitle = !compData.displayTitle;
searchInputText.value = ! searchInputText.value;
};
return { results, searchInputText, toggleDisplay };
}
}
</script>
Both ref
and reactive
can make a value reactive, but they have slight differences in use and access. ref
can directly be assigned to a single variable or constants, while reactive
can be used as the usual data
function we use often in Vue 2.0, it will make the whole Object it covers to be reactive. If you can see above, ref
will need .value
for us to have access to its content, while reactive
can be accessed directly.
Another way we can implement reactivity is by wrapping all values as an object in a reactive
function. This way, if we need to have a computed value, we can directly access it without specifying .value
. For example, we will use the above code and wrap the values in reactive function, then add a computed value which accesses the result
value.
...
<script>
import { ref, reactive, computed } from "@vue/composition-api"
export default {
name: 'ComposedComponent',
setup () {
// Wrapping all values in a reactive function
const allData = reactive({
searchInputText: '',
results: 0,
resultText: computed(() => {
return `Total result: ${allData.result}
}),
})
// Accessing
const logData = () => {
console.log(allData.resultText)
};
return { allData, logData }
}
}
</script>
There is a drawback from this setup though. We will need to change how we access it in the template as well.
<template>
<div>
<p>{{ allData.total }}</p>
<p>
Data:
<span
v-for="(n, idx) in allData.apiRes"
:key="idx">
{{ n }}
</span>
</p>
<input type="text" v-model="allData.searchInputText"/>
...
</div>
</template>
Naturally, if you are familiar with ES6, we will first think that we can just spread the object upon exporting like return { ...allData, logData }
. But this will throw an error. Even if you specify it one by one like allData.total
, the value will lose its reactivity.
For this, Vue 3.0 introduces toRefs
that will do just this. The function will convert each of the object values and map it in their own ref
. With this applied, we can access the values in the template like before.
...
<script>
import { ref, reactive, computed, toRefs } from "@vue/composition-api"
export default {
name: 'ComposedComponent',
setup () {
...
return { ...toRefs(allData), logData }
}
}
</script>
If we don't need to access any other than the reactive value, we can simply do return ...toRefs(allData)
Computed and Watch
Computed values can be added by using computed
function imported from Vue, similar to reactive
. It receives a function that returns the computed value as we have previously in Options API.
import { computed } from '@vue/composition-api'
...
setup () {
const apiRes = ['Google', 'Amazon', 'Facebook', 'Uber', 'Netflix', 'Google', 'Twitter', 'Amazon']
const total = computed(() => {
return `Total data: ${apiRes.length}`
})
return { total }
}
As for the Watch, we can assign a watch function using watch
, also imported from vue
. What we can do in there is similar to what we have in previous version.
import { ref, computed, watch } from 'vue'
...
setup () {
const results = ref(0)
const searchInputText = ref('')
watch(() => {
results.value = apiRes.filter(itm => itm === searchInputText.value).length
console.log(searchInputText.value)
})
return { results, searchInputText }
}
Props
The props
is similar to previous version. But in order to have it accessible in setup
function, we need to pass it in as an argument. The no-destructure rule still applies here, as if we do it, we will lose the reactivity
<script>
...
export default {
props: {
withPadding: {
type: Boolean,
default: false,
},
},
setup (props) {
const classNames = props.withPadding ? 'padded' : ''
return { classNames }
}
</script>
File Management
Knowing above that much, some of us might think that this can make the setup
function gigantic in no time. That goes in contrast with the readability improvement theme we have here. But fear not! As handy as we have mixins previously, we can also outsource related functions into separate files. They are functions after all.
createCounter.js
import { reactive, computed, toRefs } from '@vue/composition-api'
export default function useEventSpace() {
const event = reactive({
capacity: 5,
attending: ["Hey", "Some", "Name"],
spacesLeft: computed(() => {
return event.capacity - event.attending.length
}),
})
function increase () {
event.capacity++
}
return {
...toRefs(event),
increase,
}
}
Page.vue
<script>
import createCounter from '@/../createCounter'
...
export default {
setup () {
return { ...createCounter() }
}
</script>
Emit
One change for emit
in the new version is we are now encouraged to declare it in a separate emits
property. This acts as a self-documentation of the code, ensuring that developers that come to a component they didn't make understand the relations to its parent.
Similar to props, we can also validate the payload passed and returns a Boolean as result.
<script>
...
export default {
// we can also pass an array of emit names, e.g `emits: ['eventName']`,
emits: {
inputChange: payload => {
// payload validation
return true
}
}
...
mounted () {
this.$emit('inputChange', {
// payload
})
}
</script>
Lifecycle Hooks
We can also set lifecycle hooks in setup
function by importing onXXX
beforehand. Important note on this one is we can't access this
in setup function. If we still needed for example emit to parrent on mounted, using mounted
hook from Options API seems to be the way for now.
import { onMounted, onBeforeMount } from '@vue/composition-api'
...
export default {
setup() {
onMounted(() => {
console.log('Mounted')
}
onBeforeMounted(() => {
console.log('Before Mounted')
}
}
}
Multi Root Template
As you may already know with Vue 2.x, we can only have one root element in the template
. Not anymore in Vue 3.0 though. Thanks to the Fragments feature, having only one root element is no longer mandatory.
Note: Your linter might complain about this being illegal. Mine does. Best to save this till we have proper release. But still exciting nonetheless
<template>
<div class="main-content">
<p>{{ allData.total }}</p>
...
</div>
<div class="modal">
<p>modal content</p>
...
</div>
</template>
Conclusions
Ease of code writing and readability seems to be one of the main focuses on the upcoming update of Vue. Apart from under the hood optimizations and better support of TypeScript, these are all exciting updates to look forward to. Especially they can be treated as add-on updates on an existing app rather, than complete rewrite, as the current API still supported.
There's so much more to the features listed as upcoming updates on the next version on Vue. you can see the complete list and updates over in Vue's RFCs repo here: https://github.com/vuejs/rfcs.
Other features worth their in-depth article with detailed samples. More on that will be coming.
Sources
- Vue Composition API Documentation https://composition-api.vuejs.org/api.html
- Vue Mastery's Vue 3 Essential Course https://www.vuemastery.com/courses/vue-3-essentials/why-the-composition-api/
- Academind's Video on Youtube https://www.youtube.com/watch?v=V-xK3sbc7xI
- Vue Composition API RFC https://vue-composition-api-rfc.netlify.com
- Emit RFC https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md
- Vue.js Developers - Vue 3 Tutorial (for Vue 2 Users) https://vuejsdevelopers.com/2020/03/16/vue-js-tutorial/
Top comments (0)