前言:
上一章, 帶大家分析了vue3核心響應(yīng)式API中的三個, 即reactive,ref, readonly.
本章將會帶大家分另外幾個工作中比較常用的組合式API.
1. computed 計(jì)算屬性
在vue2中, 我們是通過computed選項(xiàng)添加計(jì)算屬性的, 關(guān)于計(jì)算屬性的本質(zhì), 這里就不過多闡述了, 如果還有不了解的同學(xué), 可以去看vue2專欄中,關(guān)于computed計(jì)算屬性講解
1.1. computed 基本使用
computed組合式API, 接受一個 getter 函數(shù)作為參數(shù),返回一個只讀的響應(yīng)式 ref對象。該 ref通過 .value 暴露 getter 函數(shù)的返回值。
這句話看著有點(diǎn)拗口, 我們通過示例來分析computedAPI的使用.
示例:
<template>
<div>
<h2>computed</h2>
<div>{{ userName }}</div>
<button @click="change">修改依賴</button>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
export default defineComponent({
setup() {
// 計(jì)算屬性依賴
const firstName = ref("張")
const lastName = ref("三")
// 計(jì)算屬性返回的ref數(shù)據(jù)
const userName = computed(() => {
console.log("computed")
return firstName.value + ' ' + lastName.value
})
console.log("userName", userName)
// 修改計(jì)算屬性的依賴
const change = () => {
firstName.value = "李"
}
return { userName, change }
},
})
</script>
控制臺輸出結(jié)果:

接下來我們對代碼示例進(jìn)行分析.主要從以下以下幾點(diǎn)分析:
computed 返回值分析
通過控制臺輸出的computedAPI 返回的結(jié)果, 你會發(fā)現(xiàn), 結(jié)構(gòu)與refAPI 創(chuàng)建響應(yīng)式數(shù)據(jù)結(jié)構(gòu)極度相似. 這也說明了一點(diǎn), computed返回的數(shù)據(jù)也是具有響應(yīng)性的, 同時使用方式也與ref數(shù)據(jù), 通過.value屬性進(jìn)行操作.
computed 參數(shù)分析
讓我們將目光移入computedAPI 的參數(shù)部分, 參數(shù)是一個回調(diào)函數(shù), 這個回調(diào)函數(shù)返回一個數(shù)據(jù), 返回的數(shù)據(jù)是有兩個ref數(shù)據(jù)拼接而成. 這兩個具有響應(yīng)性的ref數(shù)據(jù)我們就稱為是computed數(shù)據(jù)的依賴.
這個回調(diào)函數(shù)就是所謂的getter函數(shù). 函數(shù)的返回值,就是computed返回的ref對象的value屬性值.
computed參數(shù)的回調(diào)函數(shù)會在初始時自動調(diào)用一次, 后續(xù)只有當(dāng)依賴項(xiàng)數(shù)據(jù)發(fā)生變化,才會促使參數(shù)getter函數(shù)重新執(zhí)行獲取最新的數(shù)據(jù). 否則computed返回ref數(shù)據(jù)無論使用多少次, 結(jié)果都是一樣的.
computed 返回?cái)?shù)據(jù)在模板上使用
computedAPI 返回的ref數(shù)據(jù)在使用上與refAPI 創(chuàng)建的數(shù)據(jù)完全一致. 在模板上使用會自動解包, 因此我們不需要在模板中使用.value
示例代碼中的修改邏輯
針對示例代碼中修改數(shù)據(jù)的邏輯, 主要在模板上綁定了click事件, 當(dāng)事件被觸發(fā)時, 會執(zhí)行事件處理函數(shù), 即change函數(shù)., ,在change函數(shù)中修改了具有響應(yīng)性的ref數(shù)據(jù), 即firstName,
而firstName作為計(jì)算屬性computed參數(shù)getter函數(shù) 依賴項(xiàng). 根據(jù)計(jì)算屬性特性, 當(dāng)依賴項(xiàng)發(fā)生變化, 會自動執(zhí)行getter函數(shù), 返回計(jì)算后最新的數(shù)據(jù).
也就意味著userName這個具有響應(yīng)性的ref數(shù)據(jù)發(fā)生了變化, 進(jìn)而觸發(fā)頁面模板重新渲染, 更新視圖
據(jù)此總結(jié): computed 返回的數(shù)據(jù)也是具有響應(yīng)性的
1.2. computed 計(jì)算屬性設(shè)置
在vue2中, 計(jì)算屬性參數(shù)可以是一個函數(shù), 如果是一個函數(shù)表示getter函數(shù). 但如果參數(shù)是一個對象, 對象可以具有getter函數(shù)和setter函數(shù).
通過vue3中computedAPI 也可以接受一個帶有 getter 和 setter 函數(shù)的對象來創(chuàng)建一個可寫的 ref 對象。
示例:
<template>
<div>
<h2>computed</h2>
<div>{{ userName }}</div>
<button @click="change">修改依賴</button>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
export default defineComponent({
setup() {
// 計(jì)算屬性依賴
const firstName = ref("張")
const lastName = ref("三")
// 計(jì)算屬性返回的ref數(shù)據(jù)
const userName = computed({
get() {
console.log('獲取計(jì)算屬性')
return firstName.value + ' ' + lastName.value
},
set(value) {
console.log('設(shè)置計(jì)算屬性',value)
const nameArr = value.split(' ')
firstName.value = nameArr[0]
lastName.value = nameArr[1]
}
})
// 修改計(jì)算屬性的依賴
const change = () => {
// firstName.value = "李"
userName.value = '李 四'
console.log(userName.value)
}
return { userName, change }
},
})
</script>
1.3. computed 最佳使用
官網(wǎng)對此也有描述, computed計(jì)算屬性最佳使用方式有兩點(diǎn):
- 計(jì)算屬性
getter函數(shù)中不應(yīng)該修改其他狀態(tài)數(shù)據(jù),或異步操作.getter函數(shù)的本質(zhì)就是根據(jù)依賴項(xiàng)計(jì)算最新的結(jié)果. - 計(jì)算屬性本身就是根據(jù)依賴項(xiàng)生成的快照信息, 具有一定的緩存作用, 因此修改的意義不大. 所以不建議使用
setter函數(shù).
2. watch 偵聽器
watch偵聽器的作用,在vue2中也分析過. 就是監(jiān)聽數(shù)據(jù)的變化, 當(dāng)數(shù)據(jù)發(fā)生變化時處理一些事情
watch偵聽器可以偵聽一個或多個響應(yīng)式數(shù)據(jù)源,并在數(shù)據(jù)源變化時調(diào)用所給的回調(diào)函數(shù)。
2.1. watch 基本使用
偵聽器watch api 接受三個參數(shù)
- 第一個參數(shù): 偵聽數(shù)據(jù)源,
- 第二個參數(shù): 偵聽數(shù)據(jù)源發(fā)生變化時執(zhí)行的回調(diào)函數(shù),回調(diào)函數(shù)接受三個參數(shù): 新值,舊值,以及清理副作用的回調(diào)函數(shù)
- 第三個參數(shù): 一個設(shè)置偵聽配置對象, 為可選參數(shù)
示例:
<template>
<div>
<h2>watch</h2>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
() => {
console.log('count', count.value)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value = 100
}
return { change }
},
})
</script>
示例描述:
- 示例中
watch的第一個參數(shù)count為偵聽數(shù)據(jù)源 -
watch第二個參數(shù)是一個回調(diào)函數(shù), 當(dāng)count值發(fā)生變化時執(zhí)行第二個參數(shù)回調(diào)函數(shù)
2.2. watch 默認(rèn)是懶偵聽的
watch 偵聽一個數(shù)據(jù)源, 在偵聽時默認(rèn)是懶偵聽的, 也就是初始時不會觸發(fā)偵聽器的回調(diào)函數(shù),只有當(dāng)數(shù)據(jù)源發(fā)生變化時才會調(diào)用回調(diào)函數(shù)
示例:
<template>
<div>
<h2>watch</h2>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
() => {
console.log('count', count.value)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value = 100
}
return { change }
},
})
</script>
通過示例會發(fā)現(xiàn),初始時watch 第二個參數(shù)回調(diào)函數(shù)不會執(zhí)行, 只有當(dāng)count 修改時才會觸發(fā)偵聽器
2.3. watch 偵聽數(shù)據(jù)源
watch 第一個參數(shù)是偵聽器的數(shù)據(jù)源。這個數(shù)據(jù)源可以是以下幾種:
一個函數(shù),返回一個值
一個
ref一個
響應(yīng)式對象或是由以上類型的值組成的數(shù)組
偵聽源為ref數(shù)據(jù)
示例:
<template>
<div>
<h2>watch</h2>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
() => {
console.log('count', count.value)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value = 100
}
return { change }
},
})
</script>
示例中count 為ref 數(shù)據(jù), 因此當(dāng)count 變化時,會觸發(fā)響應(yīng),執(zhí)行watch 偵聽的回調(diào)函數(shù)
偵聽源為reactive響應(yīng)對象
示例
<template>
<div>
<h2>watch</h2>
{{ user }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個reactive響應(yīng)數(shù)據(jù)
const user = reactive({ name: '張三', age: 20 })
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
user,
() => {
console.log('user', user)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.name = '李四'
}
return { user, change }
},
})
</script>
示例中, watch 偵聽器的數(shù)據(jù)源是一個reactive響應(yīng)式數(shù)據(jù), 因此當(dāng)數(shù)據(jù)發(fā)生變化時,watch 會執(zhí)行偵聽器的回調(diào)函數(shù)
偵聽源是一個函數(shù)
watch 偵聽數(shù)據(jù)源只能是響應(yīng)對象, 如果我們項(xiàng)監(jiān)聽響應(yīng)對象某個屬性的變化,如果屬性值是一個原始類型類型的值, 那么就會報(bào)ts就會報(bào)錯
此時就需要使用函數(shù)式寫法, 函數(shù)返回需要監(jiān)聽的數(shù)據(jù)
示例
<template>
<div>
<h2>watch</h2>
{{ user }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const user = reactive({ name: '張三', age: 20 })
// 偵聽源是函數(shù)寫法
watch(
() => user.name,
() => {
console.log('user', user)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.name = '李四'
}
return { user, change }
},
})
</script>
偵聽數(shù)據(jù)源是一個數(shù)組
以上三種用法都是監(jiān)聽一個數(shù)據(jù)的變化, 如果希望監(jiān)聽多個數(shù)據(jù)的變化, 可以使用數(shù)組的形式
偵聽數(shù)據(jù)源可以是以上三種組成的數(shù)組
示例:
<template>
<div>
<h2>watch</h2>
{{ user }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
const user = reactive({ name: '張三', age: 20 })
const person = reactive({ name: '小明' })
// 偵聽多個數(shù)據(jù)源:ref, 響應(yīng)對象, 函數(shù)組成的數(shù)組
watch(
[count, user, () => person.name],
() => {
console.log('監(jiān)聽觸發(fā)了')
}
)
// 修改任意一個數(shù)據(jù)源,都會觸發(fā)偵聽
const change = () => {
count.value = 100
// user.name = '李四'
// person.name = '李四'
}
return { user, change }
},
})
</script>
2.4. watch 回調(diào)函數(shù)
watch函數(shù)的第二個參數(shù)就是回調(diào)函數(shù), 也就是說當(dāng)偵聽源發(fā)生變化時,執(zhí)行回調(diào)函數(shù)
此回調(diào)函數(shù)接受以下幾個參數(shù):
- 偵聽數(shù)據(jù)源最新的值
- 偵聽數(shù)據(jù)源變化前的舊值
- 清理副作用的回調(diào)函數(shù)
新舊值參數(shù)
首先來看一下新值和舊值兩個參數(shù), 這是在使用watch時比較常用到的參數(shù)
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
(nv, ov) => {
console.log('新值', nv) // 100
console.log('舊值', ov) // 10
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value = 100
}
return { count, change }
},
})
</script>
示例代碼中watch 偵聽一個ref數(shù)據(jù)的變化, 當(dāng)ref數(shù)據(jù)發(fā)生變化時, 執(zhí)行watch 的回調(diào)函數(shù)(即watch的第二個參數(shù))
這個回調(diào)函數(shù)接受兩個參數(shù), 第一個是ref 數(shù)據(jù)的新值, 第二個參數(shù)是偵聽ref 數(shù)據(jù)的舊值
清理副作用的參數(shù)
接下來我們看一下回調(diào)函數(shù)第三個參數(shù)的使用: 清理副作用
watch 偵聽的回調(diào)函數(shù)中第三個參數(shù)是清理副作用的函數(shù), 此函數(shù)接受一個函數(shù)作為參數(shù)
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 延遲打印參數(shù)
const printInfo = (val: any) => {
let timer = setTimeout(() => {
console.log('val', val)
}, 3000)
// 清理定時器
const clearTimer = () => {
clearTimeout(timer)
}
return {
clearTimer
}
}
// 偵聽ref數(shù)據(jù)變化
watch(
count,
(nv, ov, onCleanup) => {
// 執(zhí)行printInfo函數(shù), 返回一個清理定時器的函數(shù)
const { clearTimer } = printInfo(nv)
// 清理副作用函數(shù)
onCleanup(clearTimer)
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value++
}
return { count, change }
},
})
</script>
示例中, 當(dāng)count 數(shù)據(jù)發(fā)生變化時,watch 執(zhí)行回調(diào)函數(shù), 在回調(diào)函數(shù)中, 調(diào)用printInfo 傳入新值,
在printInfo 函數(shù)中, 延遲三秒打印傳入的count值,并返回一個用與關(guān)閉延遲定時器的函數(shù)
在watch 回調(diào)函數(shù)中通過解構(gòu)的方式獲取到關(guān)閉定時器的函數(shù),并作為參數(shù)傳給了回調(diào)函數(shù)的第三個參數(shù)
此時當(dāng)時間不滿三秒時, count再次發(fā)生變化, 此時又一次調(diào)用watch 回調(diào)函數(shù),在此回調(diào)函數(shù)中就會清理上一次的副作用, 關(guān)閉上一次的定時器
2.5. watch 選項(xiàng)對象
watch 第三個可選的參數(shù)是一個對象,支持以下這些選項(xiàng):
immediate: 在偵聽器創(chuàng)建時立即觸發(fā)回調(diào)。第一次調(diào)用時舊值是undefineddeep: 如果源是對象,強(qiáng)制深度遍歷,以便在深層級變更時觸發(fā)回調(diào)flush: 調(diào)整回調(diào)函數(shù)的刷新時機(jī)
immediate 初始執(zhí)行監(jiān)聽函數(shù)
初始化立即執(zhí)行watch偵聽器的回調(diào)函數(shù)
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
(nv, ov) => {
console.log('新值', nv) // 100
console.log('舊值', ov) // 10
},
// 選項(xiàng)對象
{
immediate: true // 初始監(jiān)聽
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value = 100
}
return { count, change }
},
})
</script>
當(dāng)我們沒有使用immediate 選項(xiàng)時, 默認(rèn)值為false, 此時初始偵聽器回調(diào)函數(shù)不會執(zhí)行, 只有當(dāng)偵聽源發(fā)生變化時才會觸發(fā)偵聽器, 調(diào)用回調(diào)函數(shù), 新增為修改后的值, 舊值為修改之前的值
如果使用immediate 選項(xiàng),組件初始化時就會執(zhí)行偵聽器回調(diào)函數(shù), 此時回調(diào)函數(shù)新增為偵聽源初始值, 舊值為undefined
deep 深度監(jiān)聽選項(xiàng)
deep 選項(xiàng)默認(rèn)值為false, 如果設(shè)置為true時, 則表示深度監(jiān)聽, 即偵聽源對象中屬性的變化也會被偵聽到, 執(zhí)行回調(diào)函數(shù)
示例
<template>
<div>
<h2>watch</h2>
{{ user }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref, getCurrentInstance, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const user = ref({ name: '張三', age: 20 })
watch(
user,
(nv, ov,) => {
console.log('偵聽器觸發(fā)了', nv, ov)
},
{
deep: true
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.value.name = '李四'
}
return { user, change }
},
})
</script>
示例中如果沒有添加deep選項(xiàng)時, 修改ref 數(shù)據(jù)中name屬性不會觸發(fā)偵聽器, 只有整體修改時才會觸發(fā)偵聽器, 如下:
user.value = { name: "李四", age: 28 }
當(dāng)使用deep 選項(xiàng), 值設(shè)置為true時, 表示深度監(jiān)聽, 此時通過如下修改依然可以觸發(fā)偵聽器
user.value.name = '李四'
flush: 調(diào)整回調(diào)函數(shù)的刷新時機(jī)
當(dāng)你更改了響應(yīng)式狀態(tài),它可能會同時觸發(fā) Vue 組件更新(視圖更新)和偵聽器回調(diào)。
默認(rèn)情況下,偵聽器回調(diào),都會在 Vue 組件更新之前被調(diào)用。這意味著你在偵聽器回調(diào)中訪問的 DOM將是被 Vue 更新之前的狀態(tài)。
示例:
<template>
<div>
<h2>watch</h2>
<div ref="userRef"> {{ user }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const user = reactive({ name: '張三', age: 20 })
// 獲取dom節(jié)點(diǎn)
const userRef = ref()
watch(
user,
(nv, ov,) => {
console.log('偵聽器觸發(fā)了', nv, ov)
console.log('獲取dom節(jié)點(diǎn)', userRef.value)
},
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.name = '李四'
}
return { user, change, userRef }
},
})
</script>
控制臺輸出效果:

通過示例的運(yùn)行結(jié)果, 可以很明確的看到:
修改數(shù)據(jù)時,watch 監(jiān)聽的回調(diào)函數(shù)已經(jīng)執(zhí)行, 但是組件視圖并沒有更新,因此獲取的dom節(jié)點(diǎn)顯示的依然是之前的內(nèi)容,
偵聽器回調(diào)函數(shù)執(zhí)行完畢后,才會執(zhí)行組件視圖的更新, 你也可以使用onBeforeUpdate 生命周期鉤子函數(shù)驗(yàn)證.
watch偵聽器的回調(diào)函數(shù)會先于onBeforeUpdate鉤子函數(shù)的回調(diào)函數(shù)執(zhí)行.
如果想在偵聽器回調(diào)中能訪問被 Vue 組件更新之后的 DOM,你需要指明 flush: 'post' 選項(xiàng):
示例:
<template>
<div>
<h2>watch</h2>
<div ref="userRef"> {{ user }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const user = reactive({ name: '張三', age: 20 })
// 獲取dom節(jié)點(diǎn)
const userRef = ref()
watch(
user,
(nv, ov,) => {
console.log('偵聽器觸發(fā)了', nv, ov)
console.log('獲取dom節(jié)點(diǎn)', userRef.value)
},
{
flush: "post"
}
)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.name = '李四'
}
return { user, change, userRef }
},
})
</script>
此時修改數(shù)據(jù)時,控制臺輸出:

在實(shí)例中,添加了flush:'post' 選項(xiàng)后, 偵聽源修改后, 先更新了vue組件視圖, 其次才調(diào)用偵聽器的回調(diào)函數(shù).
如果你此時使用了onUpdated鉤子函數(shù), 你會發(fā)現(xiàn)具有flush:'post'選項(xiàng)的watch 回調(diào)函數(shù),在組件更新后, 但在onUpdated鉤子函數(shù)前執(zhí)行.
2.6. watch 返回值: 關(guān)閉偵聽器
watch 方法返回一個用于關(guān)閉偵聽器的函數(shù)
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
// watch 返回值stop 是一個用于關(guān)閉偵聽器的函數(shù)
cosnt stop = watch(
count,
(nv, ov) => {
console.log('新值', nv) // 100
console.log('舊值', ov) // 10
},
// 選項(xiàng)對象
{
immediate: true // 初始監(jiān)聽
}
)
// 3s 以后關(guān)閉偵聽器
setTimeout(() => {
stop()
}, 3000)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value++
}
return { user, change }
},
})
</script>
watch偵聽器函數(shù)返回值是一個用于關(guān)閉偵聽器的函數(shù), 只要這個函數(shù)一執(zhí)行, 偵聽器就會被關(guān)閉調(diào), 偵聽源數(shù)據(jù)的變化, 不會在引發(fā)watch回調(diào)函數(shù)的執(zhí)行.
3. watchEffect
在vue2只有一個watch選項(xiàng)用于偵聽數(shù)據(jù)的變化.
但在vue3中, 除了watch偵聽數(shù)據(jù)變化外, 還新增了watchEffect API , watchEffect接受一個回調(diào)函數(shù), 會立即執(zhí)行一次回調(diào)函數(shù), 并且收集回調(diào)函數(shù)中的依賴數(shù)據(jù), 以后只要依賴數(shù)據(jù)發(fā)生變化, 都會重新執(zhí)行回調(diào)函數(shù).
3.1. watchEffect 基本使用
watchEffect 接收兩個參數(shù):
- 監(jiān)聽回調(diào)函數(shù),必傳參數(shù), 依賴發(fā)生變化執(zhí)行, 默認(rèn)初始執(zhí)行
- 監(jiān)聽的配置對象
示例:
<template>
<div>
<h2>watch</h2>
<div> fullName:{{ fullName }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'
export default defineComponent({
setup() {
// state
const firstName = ref('李')
const lastName = ref('三')
const fullName = ref('李 三')
// watchEffect 回調(diào)函數(shù)依賴發(fā)生變化時重新執(zhí)行
watchEffect(() => {
console.log('watchEffect')
fullName.value = firstName.value + ' ' + lastName.value
})
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
firstName.value = '張'
}
return { change, fullName }
},
})
</script>
3.2. watcEffect 回調(diào)函數(shù)
第一個參數(shù)就是要運(yùn)行的副作用函數(shù)。這個副作用函數(shù)的參數(shù)也是一個函數(shù),用來注冊清理副作用的回調(diào)。清理回調(diào)會在該副作用下一次執(zhí)行前被調(diào)用,可以用來清理無效的副作用,例如等待中的異步請求
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 延遲打印參數(shù)
const printInfo = (val: any) => {
let timer = setTimeout(() => {
console.log('val', val)
}, 3000)
// 清理定時器
const clearTimer = () => {
clearTimeout(timer)
}
return {
clearTimer
}
}
// 偵聽ref數(shù)據(jù)變化
watch(
count,
(nv, ov, onCleanup) => {
// 執(zhí)行printInfo函數(shù), 返回一個清理定時器的函數(shù)
const { clearTimer } = printInfo(nv)
// 清理副作用函數(shù)
onCleanup(clearTimer)
},
{
immediate: true
}
)
// 上面的寫法等價于下面watchEffect 寫法
watchEffect((onCleanup) => {
// fullName2.value = firstName.value + ' ' + lastName.value
const { clearTimer } = printInfo(count.value)
onCleanup(clearTimer)
})
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value++
}
return { user, change }
},
})
</script>
3.3. watchEffect 第二個參數(shù): 配置對象
第二個參數(shù)是一個可選的選項(xiàng),可以用來調(diào)整副作用的刷新時機(jī)或調(diào)試副作用的依賴。
默認(rèn)情況下,偵聽器將在組件渲染之前執(zhí)行。設(shè)置 flush: 'post'將會使偵聽器延遲到組件渲染之后再執(zhí)行。
示例:
<template>
<div>
<h2>watch</h2>
<div ref="userRef"> {{ user }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const user = reactive({ name: '張三', age: 20 })
// 獲取dom節(jié)點(diǎn)
const userRef = ref()
watch(
user,
(nv, ov,) => {
console.log('偵聽器觸發(fā)了', nv, ov)
console.log('獲取dom節(jié)點(diǎn)', userRef.value)
},
{
flush: "post",
immediate: true
}
)
// 等價于以下watchEffect用法
watchEffect(() => {
console.log('偵聽器觸發(fā)了', user.value)
console.log('獲取dom節(jié)點(diǎn)', userRef.value)
},{
flush: "post",
})
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
user.name = '李四'
}
return { user, change, userRef }
},
})
</script>
3.4. watchEffect 返回值:關(guān)閉偵聽器的函數(shù)
watchEffect 返回值與watch 函數(shù)一樣,都是用于關(guān)閉偵聽器的函數(shù)
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
// watch 返回值stop 是一個用于關(guān)閉偵聽器的函數(shù)
cosnt stop = watch(
count,
(nv, ov) => {
console.log('新值', nv) // 100
console.log('舊值', ov) // 10
},
// 選項(xiàng)對象
{
immediate: true // 初始監(jiān)聽
}
)
上面watch 寫法等價于以下watchEffect 用法
const stop = watchEffect(() => {
console.log('count', count.value)
})
// 3s 以后關(guān)閉偵聽器
setTimeout(() => {
stop()
}, 3000)
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value++
}
return { user, change }
},
})
</script>
4. watch 與 watchEffect 區(qū)別
watch 和 watchEffect 都能響應(yīng)式地執(zhí)行有副作用的回調(diào)。它們之間的主要區(qū)別是偵聽數(shù)據(jù)的方式:
-
watch只追蹤明確偵聽的數(shù)據(jù)源。它不會追蹤任何在回調(diào)中訪問到的東西。另外,僅在數(shù)據(jù)源確實(shí)改變時才會觸發(fā)回調(diào) -
watchEffect則會在副作用發(fā)生期間追蹤依賴。它會在同步執(zhí)行過程中,自動追蹤所有能訪問到的響應(yīng)式屬性。這更方便,而且代碼往往更簡潔
5. 一次性偵聽器
vue3在3.4 版本以后增加了一個選項(xiàng)once, 該選項(xiàng)的默認(rèn)值為false, 當(dāng)設(shè)置為true時, 被偵聽源發(fā)生變化時,偵聽器的回調(diào)只會執(zhí)行一次, 執(zhí)行完畢后,立即關(guān)閉偵聽器。
示例:
<template>
<div>
<h2>watch</h2>
{{ count }}
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
export default defineComponent({
setup() {
// 創(chuàng)建一個響應(yīng)數(shù)據(jù)
const count = ref(10);
// 偵聽響應(yīng)數(shù)據(jù)變化
watch(
count,
(nv, ov) => {
console.log("新值", nv); // 100
console.log("舊值", ov); // 10
},
// 選項(xiàng)對象
{
once: true, // 只偵聽一次, 執(zhí)行完就關(guān)閉偵聽器
}
);
// 修改監(jiān)聽數(shù)據(jù)源
const change = () => {
count.value++;
};
return { count, change };
},
});
</script>
6. 結(jié)語
至此, 就把計(jì)算屬性(computed)和常用的兩個偵聽器(watch, watchEffect)給大家介紹完畢了.
學(xué)完本章你需要能夠理解一下知識點(diǎn)
-
computed,watch,watchEffect三個API的基本使用 - 理解計(jì)算屬性
computed參數(shù)值的不同 與返回值的使用, 返回值是響應(yīng)式數(shù)據(jù) - 認(rèn)識
watch偵聽器接受三個參數(shù), 偵聽源, 回調(diào)函數(shù), 選項(xiàng)對象(可選) - 理解
watch偵聽數(shù)據(jù)源的不同寫法, 回調(diào)函數(shù)的三個參數(shù)的使用, 選項(xiàng)對象中不同選項(xiàng)的功能 - 理解
watchEffect偵聽器的使用, 以及與watch偵聽器的不同
如果覺得這篇文章對你有幫助, 點(diǎn)贊關(guān)注不迷路, 你的支持是我的動力!