📢 This article was translated by gemini-2.5-flash
Project Setup
Requires Node.js 16.0 or higher. Run this command:
This command will install and execute create-vue.
setup
Execution Timing
It runs even before beforeCreate(), so this isn’t available.
1
2
3
4
5
6
7
8
9
10
| <script>
export default{
setup(){
console.log('setup', this)
},
beforeCreate(){
console.log('beforeCreate')
}
}
</script>
|
After execution, you’ll see setup undefined printed first, then beforeCreate.
Data Access
To use data or functions defined in setup() within your <template>, you must return them.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <script>
export default{
setup(){
// data
const msg = 'hello vue3'
// function
const logMsg = () => {
console.log(msg)
}
// return
return{
msg,
logMsg
}
}
}
</script>
|
Only after returning can you use them in the <template>.
1
2
3
4
| <template>
<div>{{ msg }}</div>
<button @click="logMsg">button</button>
</template>
|
Syntactic Sugar
Returning everything manually is a bit of a pain. Luckily, you can just add setup to your <script> tag, and it handles the return automatically. So, the above example becomes:
1
2
3
4
5
6
| <script setup>
const msg = 'hello vue3'
const logMsg = () => {
console.log(msg)
}
</script>
|
Of course, it still returns under the hood; you just don’t have to write it out.
Reactive Data
reactive
The reactive() function takes an object and returns a reactive object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <script setup>
import { reactive } from 'vue'
const state = reactive({
count: 100
})
const addCount = () => {
state.count++
}
</script>
<template>
<div>
<div>{{ state.count }}</div>
<button @click="addCount">+1</button>
</div>
</template>
|
ref
ref() takes either a primitive or complex type and returns a reactive object. Under the hood, it wraps the original data in an object, making it a more complex type. Essentially, it uses reactive() internally for reactivity.
Because of this, you need to access the data via .value in <script> tags. But in <template>, you can use the variable directly. Here’s the previous example rewritten with ref():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <script setup>
import { ref } from 'vue'
const c = ref(0)
const addC = () => {
c.value++
}
</script>
<template>
<div>
<div>{{ c }}</div>
<button @click="addC">+1</button>
</div>
</template>
|
In real-world development, using only ref() often leads to more flexible and consistent code.
computed
Computed properties, using computed(), are similar to Vue 2, but now they’re just functions you can call anywhere.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <script setup>
import { computed, ref } from 'vue'
const list = ref([1, 2, 3, 4, 5, 6, 7, 8])
const computedList = computed(() => {
return list.value.filter(item => item > 2)
})
</script>
<template>
<div>{{ list }}</div>
<div>{{ computedList }}</div>
</template>
|
Properties created this way are read-only. If you need a writable computed property, you’ll have to explicitly declare get() and set(). Here’s an example from the official docs:
1
2
3
4
5
6
7
8
9
10
11
12
| <script>
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
</script>
|
Source:
https://cn.vuejs.org/api/reactivity-core.html#computed
watch
The watch() function also tracks changes in one or more data sources, executing a callback when data changes. However, it now includes two additional parameters: immediate and deep.
1
2
3
| watch(count, (newValue, oldValue) => {
console.log('count changed', oldValue, newValue)
})
|
1
2
3
| watch([count, name], ([newCount, newName], [oldCount. oldName]) => {
console.log('count or name has changed', [newCount, newName], [oldCount. oldName])
})
|
Here’s an example. I’ve used simplified Pug here, but it should be easy enough to understand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| <script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('John')
const addCount = () => count.value++
const changeName = () => name.value = 'Mike'
// Watching a single data source
watch(count, (newValue, oldValue) =>{
console.log('count changed', oldValue, newValue)
})
// Watching multiple data sources
watch([count, name], (newArr, oldArr) => {
console.log('count or name changed', oldArr, newArr)
})
</script>
<template lang="pug">
div count: {{ count }}
button(@click="addCount") +1
div name: {{ name }}
button(@click="changeName") change name
</template>
|
immediate means it runs immediately. So, when the page loads, the watcher will trigger once. At this point, oldValue will be undefined.
1
2
3
4
5
| watch(count, (newValue, oldValue) =>{
console.log('count changed', oldValue, newValue)
}, {
immediate: true
})
|
deep
deep enables deep watching. By default, watch performs a shallow watch, which won’t detect changes within complex types.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <script setup>
import { ref, watch } from 'vue'
const userInfo = ref({
name: 'John',
age: 18
})
const changeUserInfo = () => {
userInfo.value.age++
}
watch(userInfo, (newValue) => {
console.log('userInfo changed', newValue)
}, {
deep: true
})
</script>
<template lang="pug">
div {{ userInfo }}
button(@click="changeUserInfo") change userInfo
</template>
|
Watching a Single Property of a Complex Type
Using deep will watch all properties of a complex type. This means the function will execute whenever any property changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| <script setup>
import { ref, watch } from 'vue'
const userInfo = ref({
name: 'John',
age: 18
})
const changeAge = () => {
userInfo.value.age++
}
const changeName = () => {
userInfo.value.name = 'Mike'
}
watch(() => userInfo.value.age, (newValue, oldValue) => {
console.log('age changed', oldValue, newValue)
})
</script>
<template lang="pug">
div {{ userInfo }}
button(@click="changeAge") change age
button(@click="changeName") change name
</template>
|
This way, it only triggers when age changes, and the return value is the age itself.
Lifecycle Hooks
Vue3 Lifecycle Comparison
| Option API | Composition API |
|---|
| beforeCreate/created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
The main difference with the Composition API is that these are now function calls, and you can call them multiple times.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <script setup>
import { onMounted } from 'vue';
// For Composition API, `beforeCreate` and `created` logic goes directly in `setup`.
const getList = () => {
console.log('Fetching data from the backend')
}
// Executes immediately when the page loads, making the request.
getList()
// Lifecycle hooks can be called multiple times; they'll execute in order.
onMounted(() => {
console.log('Logic one')
})
onMounted(() => {
console.log('Logic two')
})
</script>
|