前言:
上一章, 帶大家分析了vue3
核心響應(yīng)式API中的三個(gè), 即reactive
,ref
, readonly
.
本章將會(huì)帶大家分另外幾個(gè)工作中比較常用的組合式API.
1. computed 計(jì)算屬性
在vue2
中, 我們是通過computed
選項(xiàng)添加計(jì)算屬性的, 關(guān)于計(jì)算屬性的本質(zhì), 這里就不過多闡述了, 如果還有不了解的同學(xué), 可以去看vue2
專欄中,關(guān)于computed
計(jì)算屬性講解
1.1. computed 基本使用
computed
組合式API, 接受一個(gè) getter
函數(shù)作為參數(shù)派诬,返回一個(gè)只讀的響應(yīng)式 ref
對象默赂。該 ref
通過 .value
暴露 getter
函數(shù)的返回值括勺。
這句話看著有點(diǎn)拗口, 我們通過示例來分析computed
API的使用.
示例:
<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>
控制臺(tái)輸出結(jié)果:
接下來我們對代碼示例進(jìn)行分析.主要從以下以下幾點(diǎn)分析:
computed 返回值分析
通過控制臺(tái)輸出的computed
API 返回的結(jié)果, 你會(huì)發(fā)現(xiàn), 結(jié)構(gòu)與ref
API 創(chuàng)建響應(yīng)式數(shù)據(jù)結(jié)構(gòu)極度相似. 這也說明了一點(diǎn), computed
返回的數(shù)據(jù)也是具有響應(yīng)性的, 同時(shí)使用方式也與ref
數(shù)據(jù), 通過.value
屬性進(jìn)行操作.
computed 參數(shù)分析
讓我們將目光移入computed
API 的參數(shù)部分, 參數(shù)是一個(gè)回調(diào)函數(shù), 這個(gè)回調(diào)函數(shù)返回一個(gè)數(shù)據(jù), 返回的數(shù)據(jù)是有兩個(gè)ref
數(shù)據(jù)拼接而成. 這兩個(gè)具有響應(yīng)性的ref
數(shù)據(jù)我們就稱為是computed
數(shù)據(jù)的依賴.
這個(gè)回調(diào)函數(shù)就是所謂的getter
函數(shù). 函數(shù)的返回值,就是computed
返回的ref
對象的value
屬性值.
computed
參數(shù)的回調(diào)函數(shù)會(huì)在初始時(shí)自動(dòng)調(diào)用一次, 后續(xù)只有當(dāng)依賴項(xiàng)數(shù)據(jù)發(fā)生變化,才會(huì)促使參數(shù)getter
函數(shù)重新執(zhí)行獲取最新的數(shù)據(jù). 否則computed
返回ref
數(shù)據(jù)無論使用多少次, 結(jié)果都是一樣的.
computed 返回?cái)?shù)據(jù)在模板上使用
computed
API 返回的ref
數(shù)據(jù)在使用上與ref
API 創(chuàng)建的數(shù)據(jù)完全一致. 在模板上使用會(huì)自動(dòng)解包, 因此我們不需要在模板中使用.value
示例代碼中的修改邏輯
針對示例代碼中修改數(shù)據(jù)的邏輯, 主要在模板上綁定了click
事件, 當(dāng)事件被觸發(fā)時(shí), 會(huì)執(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ā)生變化, 會(huì)自動(dòng)執(zhí)行getter
函數(shù), 返回計(jì)算后最新的數(shù)據(jù).
也就意味著userName
這個(gè)具有響應(yīng)性的ref
數(shù)據(jù)發(fā)生了變化, 進(jìn)而觸發(fā)頁面模板重新渲染, 更新視圖
據(jù)此總結(jié): computed
返回的數(shù)據(jù)也是具有響應(yīng)性的
1.2. computed 計(jì)算屬性設(shè)置
在vue2
中, 計(jì)算屬性參數(shù)可以是一個(gè)函數(shù), 如果是一個(gè)函數(shù)表示getter
函數(shù). 但如果參數(shù)是一個(gè)對象, 對象可以具有getter
函數(shù)和setter
函數(shù).
通過vue3
中computed
API 也可以接受一個(gè)帶有 getter
和 setter
函數(shù)的對象來創(chuàng)建一個(gè)可寫的 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ā)生變化時(shí)處理一些事情
watch
偵聽器可以偵聽一個(gè)
或多個(gè)
響應(yīng)式數(shù)據(jù)源,并在數(shù)據(jù)源變化時(shí)調(diào)用所給的回調(diào)函數(shù)宛裕。
2.1. watch 基本使用
偵聽器watch
api 接受三個(gè)參數(shù)
- 第一個(gè)參數(shù): 偵聽數(shù)據(jù)源,
- 第二個(gè)參數(shù): 偵聽數(shù)據(jù)源發(fā)生變化時(shí)執(zhí)行的回調(diào)函數(shù),回調(diào)函數(shù)接受三個(gè)參數(shù): 新值,舊值,以及清理副作用的回調(diào)函數(shù)
- 第三個(gè)參數(shù): 一個(gè)設(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)建一個(gè)響應(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
的第一個(gè)參數(shù)count
為偵聽數(shù)據(jù)源 -
watch
第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù), 當(dāng)count
值發(fā)生變化時(shí)執(zhí)行第二個(gè)參數(shù)回調(diào)函數(shù)
2.2. watch 默認(rèn)是懶偵聽的
watch
偵聽一個(gè)數(shù)據(jù)源, 在偵聽時(shí)默認(rèn)是懶偵聽的, 也就是初始時(shí)不會(huì)觸發(fā)偵聽器的回調(diào)函數(shù),只有當(dāng)數(shù)據(jù)源發(fā)生變化時(shí)才會(huì)調(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)建一個(gè)響應(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>
通過示例會(huì)發(fā)現(xiàn),初始時(shí)watch
第二個(gè)參數(shù)回調(diào)函數(shù)不會(huì)執(zhí)行, 只有當(dāng)count
修改時(shí)才會(huì)觸發(fā)偵聽器
2.3. watch 偵聽數(shù)據(jù)源
watch 第一個(gè)參數(shù)是偵聽器的數(shù)據(jù)源
翰守。這個(gè)數(shù)據(jù)源可以是以下幾種:
一個(gè)函數(shù)疲酌,返回一個(gè)值
一個(gè)
ref
一個(gè)
響應(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)建一個(gè)響應(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
變化時(shí),會(huì)觸發(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)建一個(gè)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ù)源是一個(gè)reactive
響應(yīng)式數(shù)據(jù), 因此當(dāng)數(shù)據(jù)發(fā)生變化時(shí),watch
會(huì)執(zhí)行偵聽器的回調(diào)函數(shù)
偵聽源是一個(gè)函數(shù)
watch
偵聽數(shù)據(jù)源只能是響應(yīng)對象, 如果我們項(xiàng)監(jiān)聽響應(yīng)對象某個(gè)屬性的變化,如果屬性值是一個(gè)原始類型類型的值, 那么就會(huì)報(bào)ts
就會(huì)報(bào)錯(cuò)
此時(shí)就需要使用函數(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)建一個(gè)響應(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ù)源是一個(gè)數(shù)組
以上三種用法都是監(jiān)聽一個(gè)數(shù)據(jù)的變化, 如果希望監(jiān)聽多個(gè)數(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)建一個(gè)響應(yīng)數(shù)據(jù)
const count = ref(10)
const user = reactive({ name: '張三', age: 20 })
const person = reactive({ name: '小明' })
// 偵聽多個(gè)數(shù)據(jù)源:ref, 響應(yīng)對象, 函數(shù)組成的數(shù)組
watch(
[count, user, () => person.name],
() => {
console.log('監(jiān)聽觸發(fā)了')
}
)
// 修改任意一個(gè)數(shù)據(jù)源,都會(huì)觸發(fā)偵聽
const change = () => {
count.value = 100
// user.name = '李四'
// person.name = '李四'
}
return { user, change }
},
})
</script>
2.4. watch 回調(diào)函數(shù)
watch
函數(shù)的第二個(gè)參數(shù)就是回調(diào)函數(shù)
, 也就是說當(dāng)偵聽源發(fā)生變化時(shí),執(zhí)行回調(diào)函數(shù)
此回調(diào)函數(shù)接受以下幾個(gè)參數(shù):
- 偵聽數(shù)據(jù)源最新的值
- 偵聽數(shù)據(jù)源變化前的舊值
- 清理副作用的回調(diào)函數(shù)
新舊值參數(shù)
首先來看一下新值和舊值兩個(gè)參數(shù), 這是在使用watch
時(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)建一個(gè)響應(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
偵聽一個(gè)ref
數(shù)據(jù)的變化, 當(dāng)ref
數(shù)據(jù)發(fā)生變化時(shí), 執(zhí)行watch
的回調(diào)函數(shù)(即watch
的第二個(gè)參數(shù))
這個(gè)回調(diào)函數(shù)接受兩個(gè)參數(shù), 第一個(gè)是ref
數(shù)據(jù)的新值, 第二個(gè)參數(shù)是偵聽ref
數(shù)據(jù)的舊值
清理副作用的參數(shù)
接下來我們看一下回調(diào)函數(shù)第三個(gè)參數(shù)的使用: 清理副作用
watch
偵聽的回調(diào)函數(shù)中第三個(gè)參數(shù)是清理副作用的函數(shù), 此函數(shù)接受一個(gè)函數(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)建一個(gè)響應(yīng)數(shù)據(jù)
const count = ref(10)
// 延遲打印參數(shù)
const printInfo = (val: any) => {
let timer = setTimeout(() => {
console.log('val', val)
}, 3000)
// 清理定時(shí)器
const clearTimer = () => {
clearTimeout(timer)
}
return {
clearTimer
}
}
// 偵聽ref數(shù)據(jù)變化
watch(
count,
(nv, ov, onCleanup) => {
// 執(zhí)行printInfo函數(shù), 返回一個(gè)清理定時(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ā)生變化時(shí),watch
執(zhí)行回調(diào)函數(shù), 在回調(diào)函數(shù)中, 調(diào)用printInfo
傳入新值,
在printInfo
函數(shù)中, 延遲三秒打印傳入的count
值,并返回一個(gè)用與關(guān)閉延遲定時(shí)器的函數(shù)
在watch
回調(diào)函數(shù)中通過解構(gòu)的方式獲取到關(guān)閉定時(shí)器的函數(shù),并作為參數(shù)傳給了回調(diào)函數(shù)的第三個(gè)參數(shù)
此時(shí)當(dāng)時(shí)間不滿三秒時(shí), count
再次發(fā)生變化, 此時(shí)又一次調(diào)用watch
回調(diào)函數(shù),在此回調(diào)函數(shù)中就會(huì)清理上一次的副作用, 關(guān)閉上一次的定時(shí)器
2.5. watch 選項(xiàng)對象
watch 第三個(gè)可選的參數(shù)是一個(gè)對象粥诫,支持以下這些選項(xiàng):
immediate
: 在偵聽器創(chuàng)建時(shí)立即觸發(fā)回調(diào)。第一次調(diào)用時(shí)舊值是undefined
deep
: 如果源是對象谊囚,強(qiáng)制深度遍歷执赡,以便在深層級變更時(shí)觸發(fā)回調(diào)flush
: 調(diào)整回調(diào)函數(shù)的刷新時(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)建一個(gè)響應(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)時(shí), 默認(rèn)值為false
, 此時(shí)初始偵聽器回調(diào)函數(shù)不會(huì)執(zhí)行, 只有當(dāng)偵聽源發(fā)生變化時(shí)才會(huì)觸發(fā)偵聽器, 調(diào)用回調(diào)函數(shù), 新增為修改后的值, 舊值為修改之前的值
如果使用immediate
選項(xiàng),組件初始化時(shí)就會(huì)執(zhí)行偵聽器回調(diào)函數(shù), 此時(shí)回調(diào)函數(shù)新增為偵聽源初始值, 舊值為undefined
deep 深度監(jiān)聽選項(xiàng)
deep 選項(xiàng)默認(rèn)值為false
, 如果設(shè)置為true
時(shí), 則表示深度監(jiān)聽, 即偵聽源對象中屬性的變化也會(huì)被偵聽到, 執(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)建一個(gè)響應(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)時(shí), 修改ref
數(shù)據(jù)中name
屬性不會(huì)觸發(fā)偵聽器, 只有整體修改時(shí)才會(huì)觸發(fā)偵聽器, 如下:
user.value = { name: "李四", age: 28 }
當(dāng)使用deep
選項(xiàng), 值設(shè)置為true
時(shí), 表示深度監(jiān)聽, 此時(shí)通過如下修改依然可以觸發(fā)偵聽器
user.value.name = '李四'
flush: 調(diào)整回調(diào)函數(shù)的刷新時(shí)機(jī)
當(dāng)你更改了響應(yīng)式狀態(tài)奠伪,它可能會(huì)同時(shí)觸發(fā) Vue 組件更新(視圖更新)和偵聽器回調(diào)。
默認(rèn)情況下谨敛,偵聽器回調(diào)脸狸,都會(huì)在 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)建一個(gè)響應(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>
控制臺(tái)輸出效果:
通過示例的運(yùn)行結(jié)果, 可以很明確的看到:
修改數(shù)據(jù)時(shí),watch
監(jiān)聽的回調(diào)函數(shù)已經(jīng)執(zhí)行, 但是組件視圖并沒有更新,因此獲取的dom
節(jié)點(diǎn)顯示的依然是之前的內(nèi)容,
偵聽器回調(diào)函數(shù)執(zhí)行完畢后,才會(huì)執(zhí)行組件視圖的更新, 你也可以使用onBeforeUpdate
生命周期鉤子函數(shù)驗(yàn)證.
watch
偵聽器的回調(diào)函數(shù)會(huì)先于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)建一個(gè)響應(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í)修改數(shù)據(jù)時(shí),控制臺(tái)輸出:
在實(shí)例中,添加了flush:'post'
選項(xiàng)后, 偵聽源修改后, 先更新了vue
組件視圖, 其次才調(diào)用偵聽器的回調(diào)函數(shù).
如果你此時(shí)使用了onUpdated
鉤子函數(shù), 你會(huì)發(fā)現(xiàn)具有flush:'post'
選項(xiàng)的watch 回調(diào)函數(shù),在組件更新后, 但在onUpdated
鉤子函數(shù)前執(zhí)行.
2.6. watch 返回值: 關(guān)閉偵聽器
watch
方法返回一個(gè)用于關(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)建一個(gè)響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
// watch 返回值stop 是一個(gè)用于關(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ù)返回值是一個(gè)用于關(guān)閉偵聽器的函數(shù), 只要這個(gè)函數(shù)一執(zhí)行, 偵聽器就會(huì)被關(guān)閉調(diào), 偵聽源數(shù)據(jù)的變化, 不會(huì)在引發(fā)watch
回調(diào)函數(shù)的執(zhí)行.
3. watchEffect
在vue2
只有一個(gè)watch
選項(xiàng)用于偵聽數(shù)據(jù)的變化.
但在vue3
中, 除了watch
偵聽數(shù)據(jù)變化外, 還新增了watchEffect
API , watchEffect
接受一個(gè)回調(diào)函數(shù), 會(huì)立即執(zhí)行一次回調(diào)函數(shù), 并且收集回調(diào)函數(shù)中的依賴數(shù)據(jù), 以后只要依賴數(shù)據(jù)發(fā)生變化, 都會(huì)重新執(zhí)行回調(diào)函數(shù).
3.1. watchEffect 基本使用
watchEffect
接收兩個(gè)參數(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ā)生變化時(shí)重新執(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ù)
第一個(gè)參數(shù)就是要運(yùn)行的副作用函數(shù)。這個(gè)副作用函數(shù)的參數(shù)也是一個(gè)函數(shù)揭鳞,用來注冊清理副作用的回調(diào)梆奈。清理回調(diào)會(huì)在該副作用下一次執(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)建一個(gè)響應(yīng)數(shù)據(jù)
const count = ref(10)
// 延遲打印參數(shù)
const printInfo = (val: any) => {
let timer = setTimeout(() => {
console.log('val', val)
}, 3000)
// 清理定時(shí)器
const clearTimer = () => {
clearTimeout(timer)
}
return {
clearTimer
}
}
// 偵聽ref數(shù)據(jù)變化
watch(
count,
(nv, ov, onCleanup) => {
// 執(zhí)行printInfo函數(shù), 返回一個(gè)清理定時(shí)器的函數(shù)
const { clearTimer } = printInfo(nv)
// 清理副作用函數(shù)
onCleanup(clearTimer)
},
{
immediate: true
}
)
// 上面的寫法等價(jià)于下面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 第二個(gè)參數(shù): 配置對象
第二個(gè)參數(shù)是一個(gè)可選的選項(xiàng)扶镀,可以用來調(diào)整副作用的刷新時(shí)機(jī)或調(diào)試副作用的依賴焰轻。
默認(rèn)情況下,偵聽器將在組件渲染之前執(zhí)行蝠筑。設(shè)置 flush: 'post'
將會(huì)使偵聽器延遲到組件渲染之后再執(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)建一個(gè)響應(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
}
)
// 等價(jià)于以下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)建一個(gè)響應(yīng)數(shù)據(jù)
const count = ref(10)
// 偵聽響應(yīng)數(shù)據(jù)變化
// watch 返回值stop 是一個(gè)用于關(guān)閉偵聽器的函數(shù)
cosnt stop = watch(
count,
(nv, ov) => {
console.log('新值', nv) // 100
console.log('舊值', ov) // 10
},
// 選項(xiàng)對象
{
immediate: true // 初始監(jiān)聽
}
)
上面watch 寫法等價(jià)于以下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ù)源。它不會(huì)追蹤任何在回調(diào)中訪問到的東西退疫。另外鸽素,僅在數(shù)據(jù)源確實(shí)改變時(shí)才會(huì)觸發(fā)回調(diào) -
watchEffect
則會(huì)在副作用發(fā)生期間追蹤依賴。它會(huì)在同步執(zhí)行過程中棒坏,自動(dòng)追蹤所有能訪問到的響應(yīng)式屬性遭笋。這更方便,而且代碼往往更簡潔
5. 一次性偵聽器
vue3
在3.4 版本以后增加了一個(gè)選項(xiàng)once
, 該選項(xiàng)的默認(rèn)值為false
, 當(dāng)設(shè)置為true
時(shí), 被偵聽源發(fā)生變化時(shí)喂窟,偵聽器的回調(diào)只會(huì)執(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)建一個(gè)響應(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)
和常用的兩個(gè)偵聽器(watch, watchEffect)
給大家介紹完畢了.
學(xué)完本章你需要能夠理解一下知識(shí)點(diǎn)
-
computed
,watch
,watchEffect
三個(gè)API的基本使用 - 理解計(jì)算屬性
computed
參數(shù)值的不同 與返回值的使用, 返回值是響應(yīng)式數(shù)據(jù) - 認(rèn)識(shí)
watch
偵聽器接受三個(gè)參數(shù), 偵聽源, 回調(diào)函數(shù), 選項(xiàng)對象(可選) - 理解
watch
偵聽數(shù)據(jù)源的不同寫法, 回調(diào)函數(shù)的三個(gè)參數(shù)的使用, 選項(xiàng)對象中不同選項(xiàng)的功能 - 理解
watchEffect
偵聽器的使用, 以及與watch
偵聽器的不同
如果覺得這篇文章對你有幫助, 點(diǎn)贊關(guān)注不迷路, 你的支持是我的動(dòng)力!