日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

【源码系列#04】Vue3侦听器原理(Watch)

發布時間:2023/12/29 vue 50 coder
生活随笔 收集整理的這篇文章主要介紹了 【源码系列#04】Vue3侦听器原理(Watch) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

專欄分享:vue2源碼專欄,vue3源碼專欄,vue router源碼專欄,玩具項目專欄,硬核??推薦??
歡迎各位ITer關注點贊收藏??????

語法

偵聽一個或多個響應式數據源,并在數據源變化時調用所給的回調函數

const x = ref(0)
const y = ref(0)

// 單個 ref
watch(x, (newValue, oldValue) => {
  console.log(`x is ${newValue}`)
})

// getter 函數
watch(
  () => x.value + y.value,
  (newValue, oldValue) => {
    console.log(`sum of x + y is: ${newValue}`)
  }
)

// 多個來源組成的數組
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

第一個參數可以是不同形式的“數據源”:它可以是一個 ref (包括計算屬性)、一個響應式對象、一個 getter 函數、或多個數據源組成的數組

第二個參數是在發生變化時要調用的回調函數。這個回調函數接受三個參數:新值、舊值,以及一個用于注冊副作用清理的回調函數。該回調函數會在副作用下一次重新執行前調用,可以用來清除無效的副作用,例如等待中的異步請求。

第三個可選的參數是一個對象,支持以下這些選項:

  • immediate:在偵聽器創建時立即觸發回調。第一次調用時舊值是 undefined。
  • deep:如果源是對象,強制深度遍歷,以便在深層級變更時觸發回調。參考深層偵聽器。
  • flush:調整回調函數的刷新時機。參考回調的刷新時機及 watchEffect()。
  • onTrack / onTrigger:調試偵聽器的依賴。參考調試偵聽器。

源碼實現

  • @issue1 深度遞歸循環時考慮對象中有循環引用的問題

  • @issue2 兼容數據源為響應式對象和getter函數的情況

  • @issue3 immediate回調執行時機

  • @issue4 onCleanup該回調函數會在副作用下一次重新執行前調用

/**
 * @desc 遞歸循環讀取數據
 * @issue1 考慮對象中有循環引用的問題
 */
function traversal(value, set = new Set()) {
  // 第一步遞歸要有終結條件,不是對象就不在遞歸了
  if (!isObject(value)) return value

  // @issue1 處理循環引用
  if (set.has(value)) {
    return value
  }
  set.add(value)

  for (let key in value) {
    traversal(value[key], set)
  }
  return value
}

/**
 * @desc watch
 * @issue2 兼容數據源為響應式對象和getter函數的情況
 * @issue3 immediate 立即執行
 * @issue4 onCleanup:用于注冊副作用清理的回調函數。該回調函數會在副作用下一次重新執行前調用,可以用來清除無效的副作用,例如等待中的異步請求
 */
// source 是用戶傳入的對象, cb 就是對應的回調
export function watch(source, cb, { immediate } = {} as any) {
  let getter

  // @issue2
  // 是響應式數據
  if (isReactive(source)) {
    // 遞歸循環,只要循環就會訪問對象上的每一個屬性,訪問屬性的時候會收集effect
    getter = () => traversal(source)
  } else if (isRef(source)) {
    getter = () => source.value
  } else if (isFunction(source)) {
    getter = source
  }else {
    return
  }

  // 保存用戶的函數
  let cleanup
  const onCleanup = fn => {
    cleanup = fn
  }

  let oldValue
  const scheduler = () => {
    // @issue4 下一次watch開始觸發上一次watch的清理
    if (cleanup) cleanup()
    const newValue = effect.run()
    cb(newValue, oldValue, onCleanup)
    oldValue = newValue
  }

  // 在effect中訪問屬性就會依賴收集
  const effect = new ReactiveEffect(getter, scheduler) // 監控自己構造的函數,變化后重新執行scheduler

  // @issue3
  if (immediate) {
    // 需要立即執行,則立刻執行任務
    scheduler()
  }

  // 運行getter,讓getter中的每一個響應式變量都收集這個effect
  oldValue = effect.run()
}

測試代碼

循環引用

對象中存在循環引用的情況

const person = reactive({
  name: '柏成',
  age: 25,
  address: {
    province: '山東省',
    city: '濟南市',
  }
})
person.self = person

watch(
  person,
  (newValue, oldValue) => {
    console.log('person', newValue, oldValue)
  }, {
    immediate: true
  },
)

數據源

  1. 數據源為 ref 的情況,和 immediate 回調執行時機
const x = ref(1)

watch(
  x,
  (newValue, oldValue) => {
    console.log('x', newValue, oldValue)
  }, {
    immediate: true
  },
)

setTimeout(() => {
  x.value = 2
}, 100)
  1. 兼容數據源為 響應式對象getter函數 的情況,和 immediate 回調執行時機
const person = reactive({
  name: '柏成',
  age: 25,
  address: {
    province: '山東省',
    city: '濟南市',
  }
})

// person.address 對象本身及其內部每一個屬性 都收集了effect。traversal遞歸遍歷
watch(
  person.address,
  (newValue, oldValue) => {
    console.log('person.address', newValue, oldValue)
  }, {
    immediate: true
  },
)

// 注意!我們在 watch 源碼內部滿足了 isFunction 條件
// 此時只有 address 對象本身收集了effect,僅當 address 對象整體被替換時,才會觸發回調;
// 其內部屬性發生變化并不會觸發回調
watch(
  () => person.address,
  (newValue, oldValue) => {
    console.log('person.address', newValue, oldValue)
  }, {
    immediate: true
  },
)

// person.address.city 收集了 effect
watch(
  () => person.address.city,
  (newValue, oldValue) => {
    console.log('person.address.city', newValue, oldValue)
  }, {
    immediate: true
  },
)

setTimeout(() => {
  person.address.city = '青島市'
}, 100)

onCleanup

watch回調函數接受三個參數:新值、舊值,以及一個用于注冊副作用清理的回調函數(即我們的onCleanup)。該回調函數會在副作用下一次重新執行前調用,可以用來清除無效的副作用,例如等待中的異步請求。

const person = reactive({
  name: '柏成',
  age: 25
})

let timer = 3000
function getData(timer) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(timer)
    }, timer)
  })
}

// 1. 第一次調用watch的時候注入一個取消的回調
// 2. 第二次調用watch的時候會執行上一次注入的回調
// 3. 第三次調用watch會執行第二次注入的回調
// 后面的watch觸發會將上次watch中的 clear 置為true
watch(
  () => person.age,
  async (newValue, oldValue, onCleanup) => {
    let clear = false
    onCleanup(() => {
      clear = true
    })

    timer -= 1000
    let res = await getData(timer) // 第一次執行2s后渲染2000, 第二次執行1s后渲染1000, 最終應該是1000
    if (!clear) {
      document.body.innerHTML = res
    }
  },
)

person.age = 26
setTimeout(() => {
  person.age = 27
}, 0)

總結

以上是生活随笔為你收集整理的【源码系列#04】Vue3侦听器原理(Watch)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。