首页 文章 精选 留言 我的

精选列表

搜索[bug],共10000篇文章
优秀的个人博客,低调大师

Vue 3 中 watch 与 watchEffect 的依赖收集差异导致的一个隐蔽 bug

在一个后台管理项目里,我需要监听筛选条件的变化然后重新请求表格数据。当时用了 `watch` 监听一个由多个 `ref` 拼成的对象,结果发现修改其中某个 `ref` 时回调死活不触发。排查后发现是对 `watch` 和 `watchEffect` 依赖收集机制的理解有偏差:`watch` 默认只监听传入的响应式源本身,而 `watchEffect` 会自动追踪回调里用到的所有响应式依赖。这个差异在组合式 API 里很容易踩坑,尤其是把多个响应式数据组装成新对象再监听的时候。下面从实际代码出发,梳理两者的依赖收集规则、`deep` 和 `immediate` 的配合方式,以及几种常见的规避写法。 ## 问题复现 假设页面有三个筛选字段:关键词、状态、日期范围,都用 `ref` 管理。我最初写成了这样: ```ts import { ref, watch } from 'vue' const keyword = ref('') const status = ref('all') const dateRange = ref(null) const filters = { keyword: keyword.value, status: status.value, dateRange: dateRange.value, } watch(filters, (val) => { console.log('filters changed', val) fetchList(val) }) ``` 修改 `keyword.value` 时回调完全没反应。原因是 `filters` 只是一个普通对象,它在创建时就把各个 `ref` 的当前值取了出来,本身不具备响应性。`watch` 拿到的是一个普通对象,自然没法追踪任何变化。 即使加上 `deep: true` 也没用,因为 `deep` 只对响应式对象(`reactive` 或 `ref` 包裹的对象)内部的属性遍历有效,普通对象在传入的那一刻就已经和 `ref` 脱钩了。 ## watch 的依赖来源 `watch` 的响应式来源只有三种合法形式: - 一个 `ref`(包括 `computed`) - 一个 `reactive` 对象 - 一个 getter 函数 当传入 getter 时,Vue 会在内部执行这个 getter 并追踪期间访问到的响应式属性,这一点和 `watchEffect` 类似。区别在于 `watch` 只追踪 getter 返回的那个值所依赖的响应式源,不会追踪 getter 里访问但没参与返回值的那些。 所以正确写法是把要监听的数据组织进 getter 或 `reactive` 对象: ```ts // 方式 A:getter 返回新对象 watch( () => ({ keyword: keyword.value, status: status.value, dateRange: dateRange.value, }), (val) => { fetchList(val) } ) // 方式 B:用 reactive 承载 const filters = reactive({ keyword: '', status: 'all', dateRange: null as [string, string] | null, }) watch(filters, (val) => { fetchList(val) }) ``` 方式 A 里 getter 每次执行都会读取三个 `ref`,Vue 就能把它们登记为依赖。这里返回的是新对象,新旧值引用不同,所以回调每次都会触发,不需要 `deep`。但如果 getter 返回的是同一个对象引用(比如返回一个已经存在的 `reactive`),那就得看内部属性有没有变,这时才需要 `deep: true`。 方式 B 直接用 `reactive`,`watch` 默认就是浅层监听,对象属性变化不会触发,除非加 `deep: true`。不过 Vue 3.5 之后 `reactive` 对象的 `watch` 默认变成了深层,如果项目锁在 3.4 及以前,行为会不一样,这点在升级时容易忽略。 ## watchEffect 的自动收集 `watchEffect` 会在首次执行回调时同步追踪所有被读取的响应式属性。上面那段逻辑换成 `watchEffect` 就能正常工作: ```ts watchEffect(() => { const params = { keyword: keyword.value, status: status.value, dateRange: dateRange.value, } fetchList(params) }) ``` 只要回调里读了哪个 `ref`,它就被登记成依赖,任一变化都会重新执行。代价是依赖关系不够直观,回调里顺带读了别的响应式数据也会被一起追踪,容易造成意料之外的重复请求。 ## 实际项目里的选择 筛选条件变化触发请求这种场景,我更倾向用 `watch` 加 getter,因为依赖边界清晰,回调里能拿到新旧值做对比,也方便加防抖: ```ts import { watch, ref } from 'vue' import { debounce } from 'lodash-es' const keyword = ref('') const status = ref('all') const fetchList = debounce(async (params) => { const res = await api.list(params) // ... }, 300) watch( () => [keyword.value, status.value] as const, ([kw, st]) => { fetchList({ keyword: kw, status: st }) }, { immediate: true } ) ``` 返回数组比返回对象更轻,省去每次新建对象的开销;`as const` 是为了让 TS 推断出元组类型,回调参数解构时类型不会丢。 `watchEffect` 更适合依赖关系简单、不需要旧值、也不需要控制执行时机的场景,比如把某个 `props` 同步到本地状态、或者在依赖变化时写 localStorage。函数式组合里这种同步副作用用起来确实顺手。 ## 清理副作用的时机 `watch` 和 `watchEffect` 都支持在回调里注册清理函数,用于取消上一次未完成的请求: ```ts watch(keyword, async (kw, _, onCleanup) => { const controller = new AbortController() onCleanup(() => controller.abort()) const res = await fetch(`/api/search?q=${kw}`, { signal: controller.signal, }) // ... }) ``` 回调的第三个参数 `onCleanup` 会在下一次回调执行前、以及组件卸载时被调用。如果用了防抖,清理逻辑要写在防抖函数内部或者配合 `flush: 'post'`,否则 abort 的时机和实际发请求的时机对不上。 踩过这个普通对象当响应式源的坑之后,我现在写 `watch` 之前都会先确认监听目标到底是 `ref`、`reactive` 还是 getter,普通对象一律不直接传进去。

优秀的个人博客,低调大师

deepin 技术双周报丨deepin 25 集中 bug 修复、启动 x86 更多设备适配...

第九期 deepin 技术双周报已出炉,我们会简单列出 deepin 各个小组在过去两周的相关工作进展,也会阐述未来两周的大致规划,一起来看! DDE deepin 25 的需求开发已告一段落,现阶段集中在进行针对 deepin 23 和 deepin 25 的缺陷修复。具体进展与计划如下: 进展 对 DTK 的菜单、滚动条组件样式的视觉效果进行微调; 根据社区反馈,持续修复任务栏、通知等DDE组件的缺陷; 调整技术预览界面,增加重启提示对话框及处理流程; 对 QML 版控制中心的网络、个性化、更新、显示、时间日期模块进行设计调整和完善。 计划 准备 DDE 7.0 随 deepin 25 Preview 的发布。 系统研发 进展 mesa 24.3 系列软件包版本更新; wpa、network-manager 修复连接网络时的错误提示; udisks2 修复 vfat 格式 U 盘重命名中文失败问题; 仓库新增 tuned、tecla、asio、txt2tags、exim4; CVE 安全漏洞更新。 计划 软件包更新; 推进 CVE 安全漏洞修复。 内核 进展 为 x86 内核开启更多 Intel 设备支持; 导出部分 binder 驱动所需的内核符号以支持 Waydroid; 为 ARM 内核开启 ftrace 系统调用支持; 为 ARM 内核开启鲲鹏设备加解密及 ZIP 压缩算法的硬件加速支持; 6.6 内核回合多款蓝牙设备支持,包括瑞昱(Realtek) 8852BT/8852BE-VT、8922AE 及联发科 MT7920等设备; 回合上游 Intel idle 驱动对 Granite Rapid 及 Sierra Forest 平台的支持,并合入重要问题修复; 回合上游 x86 KVM 虚拟化线性地址掩码(Linear Address Masking)支持; 回合上游针对 Intel pstate 驱动的改进; 回合上游针对 Intel TPMI 驱动的更新及修复; 合入 lock_down 特性以增强对安全启动的支持; 合入来自上游外的架构及平台支持修复,包括 x86、ARM 等平台; 合入一项针对纵存科技 RAID 卡驱动的修复; 合入一项针对联发科 MT7920网卡驱动的空指针解引用问题的修复; 引入在内核日志中打印发行版、编译工具链等信息的支持; 引入 x86 架构检测到内存问题时在内核日志中打印报告的特性; 6.6 内核代码基准更新至 6.6.69。 计划 推进 SG2042 内核支持。 如果您对 deepin 的研发相关内容感兴趣,希望能够参与进来,那么欢迎您加入社区与其他贡献者们一起展开讨论。 https://matrix.to/#/#deepin-community:deepin.org https://t.me/deepin_community 相关阅读: (1)支持 deepin(深度)社区 (2)deepin 社区报告(技术双周报、月报、公告等)

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册