第五節(jié):帶你全面理解 vue3 中 computed, watch, watchEffect 組合式API的使用

前言:

上一章, 帶大家分析了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é)果:


image.png

接下來我們對代碼示例進(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ù).

通過vue3computedAPI 也可以接受一個帶有 gettersetter 函數(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ù)

  1. 第一個參數(shù): 偵聽數(shù)據(jù)源,
  2. 第二個參數(shù): 偵聽數(shù)據(jù)源發(fā)生變化時執(zhí)行的回調(diào)函數(shù),回調(diào)函數(shù)接受三個參數(shù): 新值,舊值,以及清理副作用的回調(diào)函數(shù)
  3. 第三個參數(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>

示例描述:

  1. 示例中watch的第一個參數(shù) count 為偵聽數(shù)據(jù)源
  2. 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ù)源可以是以下幾種:

  1. 一個函數(shù),返回一個值

  2. 一個 ref

  3. 一個響應(yīng)式對象

  4. 或是由以上類型的值組成的數(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>

示例中countref 數(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ù):

  1. 偵聽數(shù)據(jù)源最新的值
  2. 偵聽數(shù)據(jù)源變化前的舊值
  3. 清理副作用的回調(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):

  1. immediate: 在偵聽器創(chuàng)建時立即觸發(fā)回調(diào)。第一次調(diào)用時舊值是 undefined

  2. deep: 如果源是對象,強(qiáng)制深度遍歷,以便在深層級變更時觸發(fā)回調(diào)

  3. 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>

控制臺輸出效果:


image.png

通過示例的運(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ù)時,控制臺輸出:


image.png

在實(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ù):

  1. 監(jiān)聽回調(diào)函數(shù),必傳參數(shù), 依賴發(fā)生變化執(zhí)行, 默認(rèn)初始執(zhí)行
  2. 監(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ū)別

watchwatchEffect 都能響應(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)

  1. computed, watch,watchEffect三個API的基本使用
  2. 理解計(jì)算屬性computed參數(shù)值的不同 與返回值的使用, 返回值是響應(yīng)式數(shù)據(jù)
  3. 認(rèn)識watch偵聽器接受三個參數(shù), 偵聽源, 回調(diào)函數(shù), 選項(xiàng)對象(可選)
  4. 理解watch偵聽數(shù)據(jù)源的不同寫法, 回調(diào)函數(shù)的三個參數(shù)的使用, 選項(xiàng)對象中不同選項(xiàng)的功能
  5. 理解watchEffect偵聽器的使用, 以及與watch偵聽器的不同


如果覺得這篇文章對你有幫助, 點(diǎn)贊關(guān)注不迷路, 你的支持是我的動力!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容