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

前言:

上一章, 帶大家分析了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)拗口, 我們通過示例來分析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>

控制臺(tái)輸出結(jié)果:


image.png

接下來我們對代碼示例進(jìn)行分析.主要從以下以下幾點(diǎn)分析:

computed 返回值分析

通過控制臺(tái)輸出的computedAPI 返回的結(jié)果, 你會(huì)發(fā)現(xiàn), 結(jié)構(gòu)與refAPI 創(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ù)分析

讓我們將目光移入computedAPI 的參數(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ù)在模板上使用

computedAPI 返回的ref數(shù)據(jù)在使用上與refAPI 創(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ù).

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

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

示例描述:

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

  1. 一個(gè)函數(shù)疲酌,返回一個(gè)值

  2. 一個(gè) ref

  3. 一個(gè)響應(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)建一個(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>

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

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

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

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

  3. 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)輸出效果:


image.png

通過示例的運(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)輸出:


image.png

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

  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ā)生變化時(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ū)別

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

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


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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市质和,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饲宿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弃锐,死亡現(xiàn)場離奇詭異殿托,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旋廷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門礼搁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扎运,你說我怎么就攤上這事瑟曲《床Γ” “怎么了负拟?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長花吟。 經(jīng)常有香客問我厨姚,道長,這世上最難降的妖魔是什么谬墙? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮险耀,結(jié)果婚禮上玖喘,老公的妹妹穿的比我還像新娘。我一直安慰自己贬派,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布搞乏。 她就那樣靜靜地躺著戒努,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侍筛。 梳的紋絲不亂的頭發(fā)上撒穷,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音禽笑,去河邊找鬼入录。 笑死僚稿,一個(gè)胖子當(dāng)著我的面吹牛邀杏,可吹牛的內(nèi)容都是我干的唬血。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼脖律,長吁一口氣:“原來是場噩夢啊……” “哼腕侄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起微姊,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤分预,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后笼痹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晴裹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年救赐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片少欺。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馋贤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仿滔,到底是詐尸還是另有隱情惠毁,我是刑警寧澤鞠绰,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布飒焦,位于F島的核電站,受9級特大地震影響牺荠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灶壶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一杈曲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧担扑,春花似錦、人聲如沸渔隶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽利术。三九已至,卻和暖如春被冒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昨悼。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工跃洛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汇竭。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像两曼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子悼凑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容