Why data must be a function in Vue
By Flavio Copes
In Vue components, data must be a function that returns an object. Learn why, how shared references break state, and how createApp fits in with Vue 3.
In Vue 3 components, data must be a function because components are reused. If data were a plain object, every instance of the component would share the same state. A function returns a fresh object for each instance.
Using Vue you might surely asked yourself the question “why must data be a function that returns an object, and not just an object?”
<template>
<a @click="counter = counter + 1">{{ counter }}</a>
</template>
<script>
export default {
data() {
return {
counter: 0
}
}
}
</script>
With Composition API and <script setup>, you don’t use the data option at all. You declare state with ref or reactive instead, and each component instance gets its own closures:
<script setup>
import { ref } from 'vue'
const counter = ref(0)
</script>
The rest of this post focuses on the Options API data rule, which still applies whenever you use that style.
What happens with a plain object
The explanation is that when the component is used multiple times, if it’s not a function, but a regular object, like this:
data: {
counter: 0
}
then because of how JavaScript works, every single instance of the component will share this property.
Objects in JavaScript are passed by reference. The component definition is created once, so that data object exists once. Every instance Vue creates from the definition points to the same object in memory.
You can see the problem with plain JavaScript:
const shared = { counter: 0 }
const first = shared
const second = shared
first.counter++
second.counter //1
first and second look independent, but they reference the same object. Change one, and the other changes too.
Now picture that counter component used three times on a page. Click one counter, and all three go up together. Not what you want.
The fix
This is not what you want in 99.9% of the cases, and instead you must do:
data() {
return {
counter: 0
}
}
Every time Vue creates a new instance of the component, it calls this function and gets a brand new object. No sharing.
What about the app root?
In Vue 2, the root created with new Vue({ ... }) was a special case: data could be a plain object there, because that root existed once.
Vue 3 uses createApp instead of new Vue. The object you pass to createApp is still a component definition. Treat its data as a function, same as any other component:
import { createApp } from 'vue'
createApp({
data() {
return {
counter: 0
}
}
}).mount('#app')
In single file components, the root App.vue follows the same rule. Components are definitions meant to be instantiated many times. That’s why Vue enforces the function for Options API data, and warns you in the console if you use a plain object.
It might be non-intuitive at first, but once you accept this explanation and learn that it’s kind of harmful to your application, and a possible source of bugs, you’ll remember to always use a function for data. Or skip the issue entirely with <script setup> and ref.
Want me to talk about your product? You can sponsor this site.