Vue3 組合式 API 學習

📢 本文由 gemini-2.5-flash 翻譯

建立專案

需要 Node.js 16.0 或更高版本,請執行指令

1
npm init vue@latest

這指令將會安裝並執行 create-vue

setup

執行週期

執行時機比 beforeCreate() 還要早,所以也無法使用 this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<script>
export default{
    setup(){
        console.log('setup', this)
    },
    beforeCreate(){
        console.log('beforeCreate')
    }
}
</script>

執行後,會先印出 setup undefined 然後才是 beforeCreate

資料呼叫

如果想在 <template> 中使用 setup() 函式裡定義的資料或函式,就必須要 return

 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>

只有 return 之後,才能在 <template> 裡使用

1
2
3
4
<template>
<div>{{ msg }}</div>
<button @click="logMsg">button</button>
</template>

語法糖

每次都要 return 確實很麻煩,實際上可以直接透過在 <script> 標籤加上 setup,這樣就可以自動 return,上述範例就可以改寫為

1
2
3
4
5
6
<script setup>
const msg = 'hello vue3'
const logMsg = () => {
        console.log(msg)
    }
</script>

當然,實際上還是會 return,只是不用手動寫出來了

響應式資料

reactive

reactive() 函式接收一個物件型態的資料,並回傳一個響應式的物件

 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() 接收簡單型別或複雜型別的資料,並回傳一個響應式物件。其本質是在原有傳入資料的基礎上,在外層包裝了一個物件,使其變成更複雜的型別。其實也是透過 reactive() 實現響應式的

所以,在 <script> 中存取資料時,需要透過 .value。而在 <template> 中存取資料則直接使用變數即可。上述範例使用 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>

實際開發中,只使用 ref() 會比較彈性且統一

computed

計算屬性 computed() 與 Vue2 類似,只是變成可以任意呼叫的函式

 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>

這樣建立出來的屬性是唯讀的,不是可寫的。若需要可寫,需要明確宣告 get()set(),以下擷取自官方範例

 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>

擷取自: https://cn.vuejs.org/api/reactivity-core.html#computed

watch

watch() 函式同樣是監聽一個或多個資料的變化,當資料變化時執行回呼函式。不過多了兩個額外參數 immediatedeep

  • 單一資料
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])
})

範例,這裡我使用了簡化的 pug,不過其實也很好懂啦

 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'

// 監聽單一資料的變化
watch(count, (newValue, oldValue) =>{
    console.log('count changed', oldValue, newValue)
})

// 監聽多個資料的變化
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

immediate 表示立即執行,即在進入頁面後會立即執行一次,此時的 oldValue 會是 undefined

1
2
3
4
5
watch(count, (newValue, oldValue) =>{
    console.log('count changed', oldValue, newValue)
}, {
    immediate: true
})

deep

deep 是深度監聽,因為預設 watch 進行淺層監聽是無法監聽到複雜型別的變化的

 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>

複雜型別單一屬性監聽

使用 deep 將會對複雜型別的所有屬性進行監聽,也就是只要任一屬性發生變化,都會執行函式

 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>

這樣就只有 age 改變時才會觸發,且回傳值是 age 的值

生命週期

Vue3 的生命週期比較

Option APIComposition API
beforeCreate/createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted

與組合式 API 的不同就是變成函式呼叫,而且可以呼叫多次

 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';

// 組合式的 beforeCreate 和 created 直接撰寫
const getList = () => {
    console.log('從後端取得資料')
}

// 執行,進入頁面便請求
getList()
    
// 生命週期函式可以呼叫多次,會按照順序執行
onMounted(() => {
    console.log('邏輯一')
})
onMounted(() => {
    console.log('邏輯二')
})
</script>
This post is licensed under CC BY-NC-SA 4.0 by the author.