专栏分享: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)的更多相关文章

  1. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  2. 【Vue2.x源码系列06】计算属性computed原理

    上一章 Vue2异步更新和nextTick原理,我们介绍了 JavaScript 执行机制是什么?nextTick源码是如何实现的?以及Vue是如何异步更新渲染的? 本章目标 计算属性是如何实现的? ...

  3. JVM源码系列:ThreadMXBean 打出堆栈信息原理分析

    我们通常会使用工具jstack 去跟踪线程信息,其如何实现使用attach 的方式还是ptrace 的方式,这些可以去参考本人的博客的其他文章. 但这些方式都是外部使用的方式,如何直接使用java代码 ...

  4. Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解

    先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...

  5. Typescript | Vue3源码系列

    TypeScript 是开源的,TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript.编译出来的 JavaScript 可以运行在任何浏览器上.TypeS ...

  6. Vue.js之Vue计算属性、侦听器、样式绑定

    前言 上一篇介绍了Vue的基本概念,这一篇介绍一下Vue的基本使用. 一.搭建一个Vue程序 1.1 搭建Vue环境 搭建Vue的开发环境总共有三种方法: 引入CDN <script src=& ...

  7. 【Vue2.x源码系列07】监听器watch原理

    上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...

  8. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

  9. AOP执行增强-Spring 源码系列(5)

    AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...

  10. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

随机推荐

  1. 搭建企业知识库:基于 Wiki.js 的实践指南

    一.简介 在当今知识经济时代,企业知识库的建设变得越来越重要.它不仅有助于企业知识的沉淀和共享,还能提升员工的工作效率,促进企业的创新发展.企业知识库是企业中形成结构化文档,共享知识的集群,可以促进企 ...

  2. 在CentOS 8上安装Xrdp远程桌面服务

    如何在CentOS 8上安装Xrdp远程桌面服务 写在前面 Xrdp是Microsoft远程桌面协议(RDP)的开源实现,可让您以图形方式控制远程系统.使用RDP,您可以登录到远程计算机并创建真实的桌 ...

  3. 如何get一个终身免费续期的定制数字人?

    想拥有一个"数字分身" 吗?给你一个终身免费续期的特权. 定制周期长?训练.运营成本高?成片效果生硬?无法应用于实际场景? 随着AIGC技术的快速发展,虚拟数字人的生成效率不断提高 ...

  4. .NET C#基础(9):资源释放 - 需要介入的资源管理

    1. 什么是IDisposable?   IDisposable接口是一个用于约定可进行释放资源操作的接口,一个类实现该接口则意味着可以使用接口约定的方法Dispose来释放资源.其定义如下: pub ...

  5. 正则表达式快速入门二 :python re module 常用API介绍

    python regex module re 使用 reference regex module in python import re re.search re.search(regex, subj ...

  6. 开源XL-LightHouse与Flink、ClickHouse之类技术相比有什么优势

    Flink是一款非常优秀的流式计算框架,而ClickHouse是一款非常优秀的OLAP类引擎,它们是各自所处领域的佼佼者,这一点是毋庸置疑的.Flink除了各种流式计算场景外也必然可以用于流式统计,C ...

  7. java中有哪些并发的List?只知道一种的就太逊了

    java中有很多list,但是原生支持并发的并不多,我们在多线程的环境中如果想同时操作同一个list的时候,就涉及到了一个并发的过程,这时候我们就需要选择自带有并发属性的list,那么java中的并发 ...

  8. pandas常用的数据类型,(serises和dataform)

  9. 万字长文教你实现华为云IoT+OpenHarmony智能家居开发

    本文分享自华为云社区<华为云IoT+OpenHarmony的智能家居开发>,作者:袁睿. 一.选题说明 1. 选题为基于OpenHarmony的智能家居,应用场景为户用,受益人群为住户. ...

  10. docker bridge网络类型研究

    bridge模式是docker的默认网络模式,使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能.可以使用iptables -t nat -vnL ...