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

这篇具有很好参考价值的文章主要介绍了【源码系列#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)。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。文章来源地址https://www.toymoban.com/news/detail-760482.html

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)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入与浅谈 Vue 中计算属性和侦听器的区别和使用(Vue3版本为例)

    #五一技术创作马拉松# 计算属性 computed 和侦听器 watch 都是 Vue.js 框架中用来响应式更新视图的重要概念。在 Vue 项目开发中,这两个技术点是非常重要的,同时也是 Vue 基础中不可缺少的知识点。在面试中,计算属性 computed 和侦听器 watch 也是经常出现的考点,作为前端开发也

    2024年02月07日
    浏览(52)
  • Vue——侦听器

    目录 基本示例​ 深层侦听器​ 即时回调的侦听器​ 回调的触发时机​ this.$watch()​ 停止侦听器​        计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态

    2023年04月13日
    浏览(36)
  • Vue中watch侦听器用法

    watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用 watch第一个参数监听源 watch第二个参数回调函数cb(newVal,oldVal) watch第三个参数一个options配置项是一个对象{ immediate :true //是否立即调用一次 deep :true //是否开启深度监听 flush :“pre” // 更新时机 } flush配置项 p

    2024年02月06日
    浏览(36)
  • Vue学习-计算属性和侦听器

    1、计算属性的定义和原理 1、定义:要用的属性不存在,要通过已有属性计算得来。 2、原理:底层借助了Objcet.defineproperty方法提供的getter和setter。 3、get函数什么时候执行?   (1) 初次读取时会执行一次。   (2)当依赖的数据发生改变时会被再次调用。 4、优势:与

    2023年04月18日
    浏览(35)
  • VUE教程-基础-计算属性和侦听器

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如: 在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量  message  的翻转字符串。当你想要在模板中的多

    2024年02月17日
    浏览(39)
  • 【Spring Boot系列】- Spring Boot侦听器Listener

    什么事Web监听器?web监听器就是 Servlet中特殊的类 ,他们能帮助开发者监听web中的特定事件,比如ServletContext、HttpSession、ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控等等。web监听器的使用场景有很多;Spring的监听器是一种

    2024年02月07日
    浏览(31)
  • 二、基础篇 vue计算属性和侦听器

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如: 在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量  message  的翻转字符串。当你想要在模板中的多

    2024年01月18日
    浏览(40)
  • Vue Composition API之侦听器watch/watchEffect

    在日常的开发中,很多时候我们需要去对一些状态进行监听,比如当显示学生的成绩列表时,我们使用一个学生的学号student_num作为请求成绩的参数,如果没有监听机制,当学号student_num改变时,我们需要依赖用户的操作去刷新成绩。但是有了侦听器,我们可以通过侦听器去监

    2024年02月08日
    浏览(29)
  • 【vue2】计算属性(computed)与侦听器(watch)详解

    🥳博       主: 初映CY的前说(前端领域) 🌞个人信条: 想要变成得到,中间还有做到! 🤘本文核心: 计算属性与侦听属性的用法 目录( 文末有给大家准备好的Xmind思维导图 ) 一、计算属性computed ①默认get()方法,仅是获取值 ②不仅仅是获取值,还具有修改属性功能

    2024年01月16日
    浏览(35)
  • 微信小程序实现页面数据侦听器,类似vue的watch

    既然小程序的组件已经有Observer功能,那为什么还要手写watch功能呢? Observer只能在Component中使用,没法在Page中使用。若是想在Page中监控某一数据的变化,Observer做不到。 Observer属于小程序的新功能,只能在高版本微信使用,低版本微信无法使用。公司的小程序就因为使用了

    2024年02月03日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包