亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

VUE3數(shù)據(jù)的偵聽超詳細(xì)講解

 更新時(shí)間:2023年12月28日 09:51:41   作者:前端不加班  
在Vue3中watch特性進(jìn)行了一些改變和優(yōu)化,與computed不同,watch通常用于監(jiān)聽數(shù)據(jù)的變化,并執(zhí)行一些副作用,這篇文章主要給大家介紹了關(guān)于VUE3數(shù)據(jù)偵聽的相關(guān)資料,需要的朋友可以參考下

前言

偵聽數(shù)據(jù)變化也是組件里的一項(xiàng)重要工作,比如偵聽路由變化、偵聽參數(shù)變化等等。

Vue 3 在保留原來的 watch 功能之外,還新增了一個(gè) watchEffect 幫助更簡(jiǎn)單的進(jìn)行偵聽。

watch

在 Vue 3 ,新版的 watch 和 Vue 2 的舊版寫法對(duì)比,在使用方式上變化非常大!

回顧 Vue 2

在 Vue 2 是這樣用的,和 data 、 methods 都在同級(jí)配置:

export default {
  data() {
    return {
      // ...
    }
  },
  // 注意這里,放在 `data` 、 `methods` 同個(gè)級(jí)別
  watch: {
    // ...
  },
  methods: {
    // ...
  },
}

并且類型繁多,選項(xiàng)式 API 的類型如下:

watch: { [key: string]: string | Function | Object | Array}

聯(lián)合類型過多,意味著用法復(fù)雜,下面是個(gè)很好的例子,雖然出自 官網(wǎng) 的用法介紹,但過于繁多的用法也反映出來對(duì)初學(xué)者不太友好,初次接觸可能會(huì)覺得一頭霧水:

export default {
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 4,
      },
      e: 5,
      f: 6,
    }
  },
  watch: {
    // 偵聽頂級(jí) Property
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
    // 字符串方法名
    b: 'someMethod',
    // 該回調(diào)會(huì)在任何被偵聽的對(duì)象的 Property 改變時(shí)被調(diào)用,不論其被嵌套多深
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true,
    },
    // 偵聽單個(gè)嵌套 Property
    'c.d': function (val, oldVal) {
      // do something
    },
    // 該回調(diào)將會(huì)在偵聽開始之后被立即調(diào)用
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true,
    },
    // 可以傳入回調(diào)數(shù)組,它們會(huì)被逐一調(diào)用
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        },
        /* ... */
      },
    ],
  },
  methods: {
    someMethod() {
      console.log('b changed')
    },
    handle1() {
      console.log('handle 1 triggered')
    },
  },
}

當(dāng)然肯定也會(huì)有開發(fā)者會(huì)覺得這樣選擇多是個(gè)好事,選擇適合自己的就好,但筆者還是認(rèn)為這種寫法對(duì)于初學(xué)者來說不是那么友好,有些過于復(fù)雜化,如果一個(gè)用法可以適應(yīng)各種各樣的場(chǎng)景,豈不是更妙?

TIP

另外需要注意的是,不能使用箭頭函數(shù)來定義 Watcher 函數(shù) (例如 searchQuery: newValue => this.updateAutocomplete(newValue) )。

因?yàn)榧^函數(shù)綁定了父級(jí)作用域的上下文,所以 this 將不會(huì)按照期望指向組件實(shí)例, this.updateAutocomplete 將是 undefined 。

Vue 2 也可以通過 this.$watch() 這個(gè) API 的用法來實(shí)現(xiàn)對(duì)某個(gè)數(shù)據(jù)的偵聽,它接受三個(gè)參數(shù): source 、 callback 和 options 。

export default {
  data() {
    return {
      a: 1,
    }
  },
  // 生命周期鉤子
  mounted() {
    this.$watch('a', (newVal, oldVal) => {
      // ...
    })
  },
}

由于 this.$watch 的用法和 Vue 3 比較接近,所以這里不做過多的回顧,請(qǐng)直接看 了解 Vue 3 部分。

了解 Vue 3

在 Vue 3 的組合式 API 寫法, watch 是一個(gè)可以接受 3 個(gè)參數(shù)的函數(shù)(保留了 Vue 2 的 this.$watch 這種用法),在使用層面上簡(jiǎn)單了很多。

import { watch } from 'vue'

// 一個(gè)用法走天下
watch(
  source, // 必傳,要偵聽的數(shù)據(jù)源
  callback // 必傳,偵聽到變化后要執(zhí)行的回調(diào)函數(shù)
  // options // 可選,一些偵聽選項(xiàng)
)

下面的內(nèi)容都基于 Vue 3 的組合式 API 用法展開講解。

API 的 TS 類型

在了解用法之前,先對(duì)它的 TS 類型聲明做一個(gè)簡(jiǎn)單的了解, watch 作為組合式 API ,根據(jù)使用方式有兩種類型聲明:

1.基礎(chǔ)用法的 TS 類型,詳見 基礎(chǔ)用法 部分

// watch 部分的 TS 類型
// ...
export declare function watch<T, Immediate extends Readonly<boolean> = false>(
  source: WatchSource<T>,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle
// ...

2.批量偵聽的 TS 類型,詳見 批量偵聽 部分

// watch 部分的 TS 類型
// ...
export declare function watch<
  T extends MultiWatchSources,
  Immediate extends Readonly<boolean> = false
>(
  sources: [...T],
  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  options?: WatchOptions<Immediate>
): WatchStopHandle

// MultiWatchSources 是一個(gè)數(shù)組
declare type MultiWatchSources = (WatchSource<unknown> | object)[]
// ...

但是不管是基礎(chǔ)用法還是批量偵聽,可以看到這個(gè) API 都是接受三個(gè)入?yún)ⅲ?/p>

并返回一個(gè)可以用來停止偵聽的函數(shù)(詳見:停止偵聽)。

要偵聽的數(shù)據(jù)源

在上面 API 的 TS 類型 已經(jīng)對(duì) watch API 的組成有一定的了解了,這里先對(duì)數(shù)據(jù)源的類型和使用限制做下說明。

TIP

如果不提前了解,在使用的過程中可能會(huì)遇到 “偵聽了但沒有反應(yīng)” 的情況出現(xiàn)。

另外,這部分內(nèi)容會(huì)先圍繞基礎(chǔ)用法展開說明,批量偵聽會(huì)在 批量偵聽 部分單獨(dú)說明。

watch API 的第 1 個(gè)參數(shù) source 是要偵聽的數(shù)據(jù)源,它的 TS 類型如下:

// watch 第 1 個(gè)入?yún)⒌?TS 類型
// ...
export declare type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
// ...

可以看到能夠用于偵聽的數(shù)據(jù),是通過 響應(yīng)式 API 定義的變量( Ref ),或者是一個(gè) 計(jì)算數(shù)據(jù) ( ComputedRef ),或者是一個(gè) getter 函數(shù) ( () => T )。

所以要想定義的 watch 能夠做出預(yù)期的行為,數(shù)據(jù)源必須具備響應(yīng)性或者是一個(gè) getter ,如果只是通過 let 定義一個(gè)普通變量,然后去改變這個(gè)變量的值,這樣是無法偵聽的。

TIP

如果要偵聽響應(yīng)式對(duì)象里面的某個(gè)值(這種情況下對(duì)象本身是響應(yīng)式,
但它的 property 不是),
需要寫成 getter 函數(shù),
簡(jiǎn)單的說就是需要寫成有返回值的函數(shù),
這個(gè)函數(shù) return 要偵聽的數(shù)據(jù), e.g. () => foo.bar ,
可以結(jié)合下方 基礎(chǔ)用法 的例子一起理解。

偵聽后的回調(diào)函數(shù)

在上面 API 的 TS 類型 介紹了 watch API 的組成,和數(shù)據(jù)源一樣,先了解一下回調(diào)函數(shù)的定義。

TIP

和數(shù)據(jù)源部分一樣,回調(diào)函數(shù)的內(nèi)容也是會(huì)先圍繞基礎(chǔ)用法展開說明,
批量偵聽會(huì)在 批量偵聽 部分單獨(dú)說明。

watch API 的第 2 個(gè)參數(shù) callback 是偵聽到數(shù)據(jù)變化時(shí)要做出的行為,它的 TS 類型如下:

// watch 第 2 個(gè)入?yún)⒌?TS 類型
// ...
export declare type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onCleanup: OnCleanup
) => any
// ...

乍一看它有三個(gè)參數(shù),但實(shí)際上這些參數(shù)不是自己定義的,而是 watch API 傳給的,所以不管用或者不用,它們都在那里:

注意:第一個(gè)參數(shù)是新值,第二個(gè)才是原來的舊值!

如同其他 JS 函數(shù),在使用 watch 的回調(diào)函數(shù)時(shí),可以對(duì)這三個(gè)參數(shù)任意命名,比如把 value 命名為覺得更容易理解的 newValue 。

TIP

如果偵聽的數(shù)據(jù)源是一個(gè) 引用類型 時(shí)( e.g. Object 、 Array 、 Date … ), 
value 和 oldValue 是完全相同的,因?yàn)橹赶蛲粋€(gè)對(duì)象。

另外,默認(rèn)情況下,watch 是惰性的,也就是只有當(dāng)被偵聽的數(shù)據(jù)源發(fā)生變化時(shí)才執(zhí)行回調(diào)。

基礎(chǔ)用法

來到這里,對(duì) 2 個(gè)必傳的參數(shù)都有一定的了解了,先看看基礎(chǔ)的用法,也就是日常最常編寫的方案,只需要先關(guān)注前 2 個(gè)必傳的參數(shù)。

// 不要忘了導(dǎo)入要用的 API
import {defineComponent,reactive ,watch} from 'vue'
export default defineComponent({
 setup(){
 //定義一個(gè)響應(yīng)式數(shù)據(jù)
 const userInfo=reactive({
 name:'Petter',
 age:18
 })
 //2s后改變數(shù)據(jù)
 setTimeout(()=>{
 userInfo.name='tom'
 },2000)
 /**
     * 可以直接偵聽這個(gè)響應(yīng)式對(duì)象
     * callback 的參數(shù)如果不用可以不寫
     */
   watch(userInfo,()=>{
       console.log('偵聽整個(gè) userInfo ', userInfo.name)
   })
      /**
     * 也可以偵聽對(duì)象里面的某個(gè)值
     * 此時(shí)數(shù)據(jù)源需要寫成 getter 函數(shù)
     */
    watch(
    //數(shù)據(jù)源,getter形式
    ()=>userInfo.name,
    // 回調(diào)函數(shù) callback
    (newValue, oldValue) => {
        console.log('只偵聽 name 的變化 ', userInfo.name)
        console.log('打印變化前后的值', { oldValue, newValue })
      }
    )
 }
})

一般的業(yè)務(wù)場(chǎng)景,基礎(chǔ)用法足以面對(duì)。

如果有多個(gè)數(shù)據(jù)源要偵聽,并且偵聽到變化后要執(zhí)行的行為一樣,那么可以使用 批量偵聽 。

特殊的情況下,可以搭配 偵聽的選項(xiàng) 做一些特殊的用法,詳見下面部分的內(nèi)容。

批量偵聽

如果有多個(gè)數(shù)據(jù)源要偵聽,并且偵聽到變化后要執(zhí)行的行為一樣,第一反應(yīng)可能是這樣來寫:

1.抽離相同的處理行為為公共函數(shù)

2.然后定義多個(gè)偵聽操作,傳入這個(gè)公共函數(shù)

import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    const message = ref<string>('')
    const index = ref<number>(0)

    // 2s后改變數(shù)據(jù)
    setTimeout(() => {
      // 來到這里才會(huì)觸發(fā) watch 的回調(diào)
      message.value = 'Hello World!'
      index.value++
    }, 2000)

    // 抽離相同的處理行為為公共函數(shù)
    const handleWatch = (
      newValue: string | number,
      oldValue: string | number
    ): void => {
      console.log({ newValue, oldValue })
    }

    // 然后定義多個(gè)偵聽操作,傳入這個(gè)公共函數(shù)
    watch(message, handleWatch)
    watch(index, handleWatch)
  },
})

這樣寫其實(shí)沒什么問題,不過除了抽離公共代碼的寫法之外, watch API 還提供了一個(gè)批量偵聽的用法,和 基礎(chǔ)用法 的區(qū)別在于,數(shù)據(jù)源和回調(diào)參數(shù)都變成了數(shù)組的形式。

數(shù)據(jù)源:以數(shù)組的形式傳入,里面每一項(xiàng)都是一個(gè)響應(yīng)式數(shù)據(jù)。

回調(diào)參數(shù):原來的 value 和 newValue 也都變成了數(shù)組,每個(gè)數(shù)組里面的順序和數(shù)據(jù)源數(shù)組排序一致。

可以看下面的這個(gè)例子更為直觀:

import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
    setup(){
      //定義多個(gè)數(shù)據(jù)源
      const message = ref<string>('')
      const index = ref<number>(0)

      //2s后改變數(shù)據(jù)
      setTimeout(()=>{
       message.value = 'Hello World!'
       index.value++
      },2000)
      watch(
       //數(shù)據(jù)源改成了數(shù)組
       [message, index],
       //回調(diào)的入?yún)⒁沧兂闪藬?shù)組,每個(gè)數(shù)組里面的順序和數(shù)據(jù)源數(shù)組排序一致
       ([newMessage, newIndex], [oldMessage, oldIndex])=>{
         console.log('message 的變化', { newMessage, oldMessage })
        console.log('index 的變化', { newIndex, oldIndex })
       }
       
      )
    }
})

什么情況下可能會(huì)用到批量偵聽呢?比如一個(gè)子組件有多個(gè) props ,當(dāng)有任意一個(gè) prop 發(fā)生變化時(shí),都需要執(zhí)行初始化函數(shù)重置組件的狀態(tài),那么這個(gè)時(shí)候就可以用上這個(gè)功能啦!

TIP

在適當(dāng)?shù)臉I(yè)務(wù)場(chǎng)景,
也可以使用 watchEffect 來完成批量偵聽,
但請(qǐng)留意 功能區(qū)別 部分的說明。

偵聽的選項(xiàng)

在 API 的 TS 類型 里提到, watch API 還接受第 3 個(gè)參數(shù) options ,可選的一些偵聽選項(xiàng)。

它的 TS 類型如下:

// watch 第 3 個(gè)入?yún)⒌?TS 類型
// ...
export declare interface WatchOptions<Immediate = boolean>
  extends WatchOptionsBase {
  immediate?: Immediate
  deep?: boolean
}
// ...

// 繼承的 base 類型
export declare interface WatchOptionsBase extends DebuggerOptions {
  flush?: 'pre' | 'post' | 'sync'
}
// ...

// 繼承的 debugger 選項(xiàng)類型
export declare interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
// ...

options 是一個(gè)對(duì)象的形式傳入,有以下幾個(gè)選項(xiàng):

其中 onTrack 和 onTrigger 的 e 是 debugger 事件,建議在回調(diào)內(nèi)放置一個(gè) debugger 語句 以調(diào)試依賴,這兩個(gè)選項(xiàng)僅在開發(fā)模式下生效。

TIP

deep 默認(rèn)是 false ,
但是在偵聽 reactive 對(duì)象或數(shù)組時(shí),會(huì)默認(rèn)為 true ,
詳見 偵聽選項(xiàng)之 deep。

偵聽選項(xiàng)之 deep

deep 選項(xiàng)接受一個(gè)布爾值,可以設(shè)置為 true 開啟深度偵聽,或者是 false 關(guān)閉深度偵聽,默認(rèn)情況下這個(gè)選項(xiàng)是 false 關(guān)閉深度偵聽的,但也存在特例。

設(shè)置為 false 的情況下,如果直接偵聽一個(gè)響應(yīng)式的 引用類型 數(shù)據(jù)(e.g. Object 、 Array … ),雖然它的屬性的值有變化,但對(duì)其本身來說是不變的,所以不會(huì)觸發(fā) watch 的 callback 。

下面是一個(gè)關(guān)閉了深度偵聽的例子:

import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 定義一個(gè)響應(yīng)式數(shù)據(jù),注意用的是 ref 來定義
    const nums = ref<number[]>([])

    // 2s后給這個(gè)數(shù)組添加項(xiàng)目
    setTimeout(() => {
      nums.value.push(1)

      // 可以打印一下,確保數(shù)據(jù)確實(shí)變化了
      console.log('修改后', nums.value)
    }, 2000)

    // 但是這個(gè) watch 不會(huì)按預(yù)期執(zhí)行
    watch(
      nums,
      // 這里的 callback 不會(huì)被觸發(fā)
      () => {
        console.log('觸發(fā)偵聽', nums.value)
      },
      // 因?yàn)殛P(guān)閉了 deep
      {
        deep: false,
      }
    )
  },
})

類似這種情況,需要把 deep 設(shè)置為 true 才可以觸發(fā)偵聽。

可以看到上面的例子特地用了 ref API ,這是因?yàn)橥ㄟ^ reactive API 定義的對(duì)象無法將 deep 成功設(shè)置為 false (這一點(diǎn)在目前的官網(wǎng)文檔未找到說明,最終是在 watch API 的源碼 上找到了答案)。

// ...
if (isReactive(source)) {
  getter = () => source
  deep = true // 被強(qiáng)制開啟了
}
// ...

這個(gè)情況就是上面所說的 “特例” ,可以通過 isReactive API 來判斷是否需要手動(dòng)開啟深度偵聽。

// 導(dǎo)入 isReactive API
import { defineComponent, isReactive, reactive, ref } from 'vue'

export default defineComponent({
  setup() {
    // 偵聽這個(gè)數(shù)據(jù)時(shí),會(huì)默認(rèn)開啟深度偵聽
    const foo = reactive({
      name: 'Petter',
      age: 18,
    })
    console.log(isReactive(foo)) // true

    // 偵聽這個(gè)數(shù)據(jù)時(shí),不會(huì)默認(rèn)開啟深度偵聽
    const bar = ref({
      name: 'Petter',
      age: 18,
    })
    console.log(isReactive(bar)) // false
  },
})

偵聽選項(xiàng)之 immediate

在 偵聽后的回調(diào)函數(shù) 部分有了解過, watch 默認(rèn)是惰性的,也就是只有當(dāng)被偵聽的數(shù)據(jù)源發(fā)生變化時(shí)才執(zhí)行回調(diào)。

這句話是什么意思呢?來看一下這段代碼,為了減少 deep 選項(xiàng)的干擾,換一個(gè)類型,換成 string 數(shù)據(jù)來演示,請(qǐng)留意注釋:

import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 這個(gè)時(shí)候不會(huì)觸發(fā) watch 的回調(diào)
    const message = ref<string>('')

    // 2s后改變數(shù)據(jù)
    setTimeout(() => {
      // 來到這里才會(huì)觸發(fā) watch 的回調(diào)
      message.value = 'Hello World!'
    }, 2000)

    watch(message, () => {
      console.log('觸發(fā)偵聽', message.value)
    })
  },
})

可以看到,數(shù)據(jù)在初始化的時(shí)候并不會(huì)觸發(fā)偵聽回調(diào),如果有需要的話,通過 immediate 選項(xiàng)來讓它直接觸發(fā)。

immediate 選項(xiàng)接受一個(gè)布爾值,默認(rèn)是 false ,可以設(shè)置為 true 讓回調(diào)立即執(zhí)行。

改成這樣,請(qǐng)留意高亮的代碼部分和新的注釋:

import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 這一次在這里可以會(huì)觸發(fā) watch 的回調(diào)了
    const message = ref<string>('')

    // 2s后改變數(shù)據(jù)
    setTimeout(() => {
      // 這一次,這里是第二次觸發(fā) watch 的回調(diào),不再是第一次
      message.value = 'Hello World!'
    }, 2000)

    watch(
      message,
      () => {
        console.log('觸發(fā)偵聽', message.value)
      },
      // 設(shè)置 immediate 選項(xiàng)
      {
        immediate: true,
      }
    )
  },
})

注意,在帶有 immediate 選項(xiàng)時(shí),不能在第一次回調(diào)時(shí)取消該數(shù)據(jù)源的偵聽,詳見 停止偵聽 部分。

偵聽選項(xiàng)之 flush

flush 選項(xiàng)是用來控制 偵聽回調(diào) 的調(diào)用時(shí)機(jī),接受指定的字符串,可選值如下,默認(rèn)是 ‘pre’ 。

對(duì)于 ‘pre’ 和 ‘post’ ,回調(diào)使用隊(duì)列進(jìn)行緩沖?;卣{(diào)只被添加到隊(duì)列中一次。

即使觀察值變化了多次,值的中間變化將被跳過,不會(huì)傳遞給回調(diào),這樣做不僅可以提高性能,還有助于保證數(shù)據(jù)的一致性。

更多關(guān)于 flush 的信息,請(qǐng)參閱 回調(diào)的觸發(fā)時(shí)機(jī) 。

停止偵聽

如果在 setup 或者 script-setup 里使用 watch 的話, 組件被卸載 的時(shí)候也會(huì)一起被停止,一般情況下不太需要關(guān)心如何停止偵聽。

不過有時(shí)候可能想要手動(dòng)取消, Vue 3 也提供了方法。

TIP

隨著組件被卸載一起停止的前提是,偵聽器必須是 同步語句 創(chuàng)建的,
這種情況下偵聽器會(huì)綁定在當(dāng)前組件上。

如果放在 setTimeout 等 異步函數(shù) 里面創(chuàng)建,
則不會(huì)綁定到當(dāng)前組件,因此組件卸載的時(shí)候不會(huì)一起停止該偵聽器,
這種時(shí)候就需要手動(dòng)停止偵聽。

在 API 的 TS 類型 有提到,當(dāng)在定義一個(gè) watch 行為的時(shí)候,它會(huì)返回一個(gè)用來停止偵聽的函數(shù)。

這個(gè)函數(shù)的 TS 類型如下:

export declare type WatchStopHandle = () => void

用法很簡(jiǎn)單,做一下簡(jiǎn)單了解即可:

// 定義一個(gè)取消觀察的變量,它是一個(gè)函數(shù)
const unwatch = watch(message, () => {
  // ...
})

// 在合適的時(shí)期調(diào)用它,可以取消這個(gè)偵聽
unwatch()

但是也有一點(diǎn)需要注意的是,如果啟用了 immediate 選項(xiàng) ,不能在第一次觸發(fā)偵聽回調(diào)時(shí)執(zhí)行它。

// 注意:這是一段錯(cuò)誤的代碼,運(yùn)行會(huì)報(bào)錯(cuò)
const unwatch = watch(
  message,
  // 偵聽的回調(diào)
  () => {
    // ...
    // 在這里調(diào)用會(huì)有問題 ?
    unwatch()
  },
  // 啟用 immediate 選項(xiàng)
  {
    immediate: true,
  }
)

會(huì)收獲一段報(bào)錯(cuò),告訴 unwatch 這個(gè)變量在初始化前無法被訪問:

Uncaught ReferenceError: Cannot access 'unwatch' before initialization

目前有兩種方案可以讓實(shí)現(xiàn)這個(gè)操作:

方案一:使用 var 并判斷變量類型,利用 var 的變量提升 來實(shí)現(xiàn)目的。

// 這里改成 var ,不要用 const 或 let
var unwatch = watch(
  message,
  // 偵聽回調(diào)
  () => {
    // 這里加一個(gè)判斷,是函數(shù)才執(zhí)行它
    if (typeof unwatch === 'function') {
      unwatch()
    }
  },
  // 偵聽選項(xiàng)
  {
    immediate: true,
  }
)

不過 var 已經(jīng)屬于過時(shí)的語句了,建議用方案二的 let 。

方案二:使用 let 并判斷變量類型。

// 如果不想用 any ,可以導(dǎo)入 TS 類型
import type { WatchStopHandle } from 'vue'

// 這里改成 let ,但是要另起一行,先定義,再賦值
let unwatch: WatchStopHandle
unwatch = watch(
  message,
  // 偵聽回調(diào)
  () => {
    // 這里加一個(gè)判斷,是函數(shù)才執(zhí)行它
    if (typeof unwatch === 'function') {
      unwatch()
    }
  },
  // 偵聽選項(xiàng)
  {
    immediate: true,
  }
)

偵聽效果清理

在 偵聽后的回調(diào)函數(shù) 部分提及到一個(gè)參數(shù) onCleanup ,它可以幫注冊(cè)一個(gè)清理函數(shù)。

有時(shí) watch 的回調(diào)會(huì)執(zhí)行異步操作,當(dāng) watch 到數(shù)據(jù)變更的時(shí)候,需要取消這些操作,這個(gè)函數(shù)的作用就用于此,會(huì)在以下情況調(diào)用這個(gè)清理函數(shù):

watcher 即將重新運(yùn)行的時(shí)候

watcher 被停止(組件被卸載或者被手動(dòng) 停止偵聽 )

TS 類型:

declare type OnCleanup = (cleanupFn: () => void) => void

用法方面比較簡(jiǎn)單,傳入一個(gè)回調(diào)函數(shù)運(yùn)行即可,不過需要注意的是,需要在停止偵聽之前注冊(cè)好清理行為,否則不會(huì)生效。

在 停止偵聽 里的最后一個(gè) immediate 例子的基礎(chǔ)上繼續(xù)添加代碼,請(qǐng)注意注冊(cè)的時(shí)機(jī):

let unwatch: WatchStopHandle
unwatch = watch(
  message,
  (newValue, oldValue, onCleanup) => {
    // 需要在停止偵聽之前注冊(cè)好清理行為
    onCleanup(() => {
      console.log('偵聽清理ing')
      // 根據(jù)實(shí)際的業(yè)務(wù)情況定義一些清理操作 ...
    })
    // 然后再停止偵聽
    if (typeof unwatch === 'function') {
      unwatch()
    }
  },
  {
    immediate: true,
  }
)

watchEffect

如果一個(gè)函數(shù)里包含了多個(gè)需要偵聽的數(shù)據(jù),一個(gè)一個(gè)數(shù)據(jù)去偵聽太麻煩了,在 Vue 3 ,可以直接使用 watchEffect API 來簡(jiǎn)化的操作。

API 的 TS 類型

這個(gè) API 的類型如下,使用的時(shí)候需要傳入一個(gè)副作用函數(shù)(相當(dāng)于 watch 的 偵聽后的回調(diào)函數(shù) ),也可以根據(jù)的實(shí)際情況傳入一些可選的 偵聽選項(xiàng) 。

和 watch API 一樣,它也會(huì)返回一個(gè)用于 停止偵聽 的函數(shù)。

// watchEffect 部分的 TS 類型
// ...
export declare type WatchEffect = (onCleanup: OnCleanup) => void

export declare function watchEffect(
  effect: WatchEffect,
  options?: WatchOptionsBase
): WatchStopHandle
// ...

副作用函數(shù)也會(huì)傳入一個(gè)清理回調(diào)作為參數(shù),和 watch 的 偵聽效果清理 一樣的用法。

可以理解為它是一個(gè)簡(jiǎn)化版的 watch ,具體簡(jiǎn)化在哪里呢?請(qǐng)看下面的用法示例。

用法示例

它立即執(zhí)行傳入的一個(gè)函數(shù),同時(shí)響應(yīng)式追蹤其依賴,并在其依賴變更時(shí)重新運(yùn)行該函數(shù)。

import { defineComponent, ref, watchEffect } from 'vue'

export default defineComponent({
  setup() {
    // 單獨(dú)定義兩個(gè)數(shù)據(jù),后面用來分開改變數(shù)值
    const name = ref<string>('Petter')
    const age = ref<number>(18)

    // 定義一個(gè)調(diào)用這兩個(gè)數(shù)據(jù)的函數(shù)
    const getUserInfo = (): void => {
      console.log({
        name: name.value,
        age: age.value,
      })
    }

    // 2s后改變第一個(gè)數(shù)據(jù)
    setTimeout(() => {
      name.value = 'Tom'
    }, 2000)

    // 4s后改變第二個(gè)數(shù)據(jù)
    setTimeout(() => {
      age.value = 20
    }, 4000)

    // 直接偵聽調(diào)用函數(shù),在每個(gè)數(shù)據(jù)產(chǎn)生變化的時(shí)候,它都會(huì)自動(dòng)執(zhí)行
    watchEffect(getUserInfo)
  },
})

和 watch 的區(qū)別

雖然理論上 watchEffect 是 watch 的一個(gè)簡(jiǎn)化操作,可以用來代替 批量偵聽 ,但它們也有一定的區(qū)別:

1.watch 可以訪問偵聽狀態(tài)變化前后的值,而 watchEffect 沒有。

2.watch 是在屬性改變的時(shí)候才執(zhí)行,而 watchEffect 則默認(rèn)會(huì)執(zhí)行一次,然后在屬性改變的時(shí)候也會(huì)執(zhí)行。

第二點(diǎn)的意思,看下面這段代碼可以有更直觀的理解:

使用 watch :

export default defineComponent({
  setup() {
    const foo = ref<string>('')

    setTimeout(() => {
      foo.value = 'Hello World!'
    }, 2000)

    function bar() {
      console.log(foo.value)
    }

    // 使用 watch 需要先手動(dòng)執(zhí)行一次
    bar()

    // 然后當(dāng) foo 有變動(dòng)時(shí),才會(huì)通過 watch 來執(zhí)行 bar()
    watch(foo, bar)
  },
})

使用 watchEffect :

export default defineComponent({
  setup() {
    const foo = ref<string>('')

    setTimeout(() => {
      foo.value = 'Hello World!'
    }, 2000)

    function bar() {
      console.log(foo.value)
    }

    // 可以通過 watchEffect 實(shí)現(xiàn) bar() + watch(foo, bar) 的效果
    watchEffect(bar)
  },
})

可用的偵聽選項(xiàng)

雖然用法和 watch 類似,但也簡(jiǎn)化了一些選項(xiàng),它的偵聽選項(xiàng) TS 類型如下:

// 只支持 base 類型
export declare interface WatchOptionsBase extends DebuggerOptions {
  flush?: 'pre' | 'post' | 'sync'
}
// ...

// 繼承的 debugger 選項(xiàng)類型
export declare interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
// ...

對(duì)比 watch API ,它不支持 deep 和 immediate ,請(qǐng)記住這一點(diǎn),其他的用法是一樣的。

flush 選項(xiàng)的使用詳見 偵聽選項(xiàng)之 flush ,onTrack 和 onTrigger 詳見 偵聽的選項(xiàng) 部分內(nèi)容。

watchPostEffect

watchEffect API 使用 flush: ‘post’ 選項(xiàng)時(shí)的別名,具體區(qū)別詳見 偵聽選項(xiàng)之 flush 部分。

TIP

Vue v3.2.0 及以上版本才支持該 API 。

watchSyncEffect

watchEffect API 使用 flush: ‘sync’ 選項(xiàng)時(shí)的別名,具體區(qū)別詳見 偵聽選項(xiàng)之 flush 部分。

TIP

Vue v3.2.0 及以上版本才支持該 API 。

總結(jié)

到此這篇關(guān)于VUE3數(shù)據(jù)的偵聽的文章就介紹到這了,更多相關(guān)VUE3數(shù)據(jù)偵聽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • nuxt.js添加環(huán)境變量,區(qū)分項(xiàng)目打包環(huán)境操作

    nuxt.js添加環(huán)境變量,區(qū)分項(xiàng)目打包環(huán)境操作

    這篇文章主要介紹了nuxt.js添加環(huán)境變量,區(qū)分項(xiàng)目打包環(huán)境操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 如何巧用Vue.extend繼承組件實(shí)現(xiàn)el-table雙擊可編輯(不使用v-if、v-else)

    如何巧用Vue.extend繼承組件實(shí)現(xiàn)el-table雙擊可編輯(不使用v-if、v-else)

    這篇文章主要給大家介紹了關(guān)于如何巧用Vue.extend繼承組件實(shí)現(xiàn)el-table雙擊可編輯的相關(guān)資料,不使用v-if、v-else,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Nuxt不同環(huán)境如何區(qū)分的方法

    Nuxt不同環(huán)境如何區(qū)分的方法

    在一般情況下,我們的項(xiàng)目肯定需要區(qū)分不同環(huán)境,那么Nuxt提供給我們這樣的基本能力了么,下面我們就一起來了解一下
    2021-05-05
  • vue中@click綁定多個(gè)事件問題(教你避坑)

    vue中@click綁定多個(gè)事件問題(教你避坑)

    這篇文章主要介紹了vue中@click綁定多個(gè)事件問題(教你避坑),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • vue ssr 指南詳讀

    vue ssr 指南詳讀

    這篇文章主要介紹了vue ssr 指南詳讀,詳細(xì)的介紹了什么是SSR以及如何使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-06-06
  • vue報(bào)錯(cuò)Failed to execute 'appendChild' on 'Node'解決

    vue報(bào)錯(cuò)Failed to execute 'appendChild&apos

    這篇文章主要為大家介紹了vue報(bào)錯(cuò)Failed to execute 'appendChild' on 'Node'解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 詳解Vue組件如何正確引用和使用外部方法

    詳解Vue組件如何正確引用和使用外部方法

    在開發(fā)Vue應(yīng)用時(shí),我們經(jīng)常需要在多個(gè)組件中復(fù)用一些通用的函數(shù)或方法,這些函數(shù)可能是我們自己編寫的工具函數(shù),也可能是從第三方庫中導(dǎo)入的,下面我們就來看看如何正確引用和使用外部方法吧
    2024-01-01
  • vue的axios和mock.js你了解嗎

    vue的axios和mock.js你了解嗎

    這篇文章主要為大家詳細(xì)介紹了vue的axios和mock.js,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • vue2使用wangeditor實(shí)現(xiàn)數(shù)學(xué)公式和富文本編輯器

    vue2使用wangeditor實(shí)現(xiàn)數(shù)學(xué)公式和富文本編輯器

    這篇文章主要為大家詳細(xì)介紹了vue2如何使用wangeditor實(shí)現(xiàn)數(shù)學(xué)公式和富文本編輯器功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-12-12
  • vue-jsonp的使用及說明

    vue-jsonp的使用及說明

    這篇文章主要介紹了vue-jsonp的使用及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評(píng)論