当前位置: 首页 > news >正文

05计算属性与定时器

8.计算属性

基础示例

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:

js

constauthor=reactive({name:'John Doe',books:['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']})

我们想根据author是否已有一些书籍来展示不同的信息:

template

<p>Has published books:</p><span>{{author.books.length>0?'Yes':'No'}}</span>

这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于author.books。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。

因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:

vue

<script setup>import{reactive,computed}from'vue'constauthor=reactive({name:'John Doe',books:['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']})// 一个计算属性 refconstpublishedBooksMessage=computed(()=>{returnauthor.books.length>0?'Yes':'No'})</script><template><p>Has published books:</p><span>{{publishedBooksMessage}}</span></template>

我们在这里定义了一个计算属性publishedBooksMessagecomputed()方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过publishedBooksMessage.value访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加.value

Vue 的计算属性会自动追踪响应式依赖。它会检测到publishedBooksMessage依赖于author.books,所以当author.books改变时,任何依赖于publishedBooksMessage的绑定都会同时更新。

计算属性缓存 vs 方法

你可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:

template

<p>{{calculateBooksMessage()}}</p>

js

// 组件中functioncalculateBooksMessage(){returnauthor.books.length>0?'Yes':'No'}

若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要author.books不改变,无论多少次访问publishedBooksMessage都会立即返回先前的计算结果,而不用重复执行 getter 函数。

这也解释了为什么下面的计算属性永远不会更新,因为Date.now()并不是一个响应式依赖

js

constnow=computed(()=>Date.now())

相比之下,方法调用总是会在重渲染发生时再次执行函数。

为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于list。没有缓存的话,我们会重复执行非常多次list的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。

Vue 3的响应式系统没有一个固定的“个数”,它更像一个由几个核心部分构成的、精巧协作的机制。要理解它,与其数“有几个”,不如看它是“怎么工作的”。

这套机制主要围绕“依赖”的收集与触发来运转,核心角色有三个:

🧩 响应式的三个核心角色

角色它是谁?核心职责
🤔 数据 (Reactive Data)通过reactive()ref()创建的对象被监听的目标。它本身是普通的JavaScript对象,但被Vue用Proxy“包装”了一层-1-8。当它的属性被读取或修改时,都会触发内部机制。
📋 依赖 (Dependency / Effect)所有依赖于数据的“副作用”等待执行的“任务清单”。它可以是组件的渲染函数computed计算属性watch侦听器,或者是开发者用watchEffect创建的函数-4-5。在Vue内部,它们都被抽象为ReactiveEffect这个类的实例-5-6。
🗺️ 依赖收集器 (TargetMap)一个用来存储依赖关系的“笔记本”记录“谁依赖了谁”。它是一个全局的WeakMap,结构大致如下:WeakMap{ 目标对象: Map{ 属性名: Set[依赖1, 依赖2] } }-5-7。它清晰地记录了每个对象的每个属性,都被哪些“依赖”所使用。

⚙️ 工作流程

这三个角色通过“收集”和“触发”两个步骤形成一个闭环:

  • 阶段一:收集依赖
    当一个副作用函数(比如组件的渲染)开始执行时,它会读取响应式数据。这时,响应式数据的Proxy拦截器(get)会调用内部的track函数-1-4。track函数就像图书管理员,它负责在“依赖收集器”(targetMap)里找到当前正在运行的副作用(即“依赖”),并把这条依赖关系记录下来-5。
  • 阶段二:触发更新
    当响应式数据的值发生变化时(比如用户点击按钮),Proxy拦截器(set)会调用内部的trigger函数-1-4。trigger函数会根据targetMap里的记录,找到所有依赖于这个属性的“依赖”们,然后依次去执行它们,从而完成视图更新或其它副作用-5-7。

💡 这和Vue 2有什么区别?

理解Vue 3的响应式,关键在于和Vue 2的对比,这能帮你更好地理解为什么它被称为“新”的:

对比维度Vue 2 的响应式Vue 3 的响应式
核心实现基于Object.defineProperty递归遍历对象属性-1-8。基于Proxy对整个对象进行代理-1-8。
动态属性无法监听对象属性的新增和删除,需要使用Vue.setVue.delete来处理-8。天然支持动态增删属性-4-8。
数组监听无法通过索引直接修改数组或修改length来触发响应式,需要对数组方法进行hack-4-8。完美支持数组的所有变更方式,包括通过索引赋值和修改length-4-8。
依赖追踪组件渲染时静态地收集依赖。通过Proxy运行时动态地、精确地收集依赖-5。

简单来说:Date.now()不是响应式依赖,因为它和Vue的响应式系统“没有建立任何连接”。它只是一个普通的JavaScript表达式,Vue无法知道它“变了”,也不知道什么时候需要重新运行依赖它的代码。

让我们从Vue 3响应式系统的三个核心角色(数据、依赖、依赖收集器)的角度,来拆解一下为什么Date.now()会被排除在外:

  1. 它不是被“代理”的数据

响应式系统的前提是数据本身是响应式的。Vue只能追踪那些经过reactive()ref()包装过的数据。

  • Date.now()是什么?
    它是在代码执行的那一瞬间,从系统时钟读取的一个静态的数字。例如const time = Date.now(),此时time就是一个普通数字常量,比如1712323456789
  • 为什么不行?
    这个数字并没有被ref(1712323456789)包装过,也没有作为属性挂载在一个reactive()对象上。它对于Vue来说,就像一个普通的const a = 1一样,是一个不会变化的原始值。
  1. 它无法触发“依赖收集”的时机

回想一下响应式的工作流程:当副作用(如渲染函数)执行时,如果它读取了响应式数据,会触发Proxyget拦截器,从而执行track函数把当前的副作用记录下来。

  • 当代码执行Date.now()时:
    这只是一个普通的函数调用,返回值是一个数字。Vue 的Proxy拦截器根本没有被触发。Vue 只知道“有人读了一个响应式对象的属性”,但不知道“有人获取了当前时间”。
  • 结果:
    依赖收集器(targetMap)里没有任何记录表明“这个副作用依赖了当前时间”。
  1. 它无法“通知”依赖更新

响应式系统另一个关键点是“变更通知”。当响应式数据变化时(通过Proxyset拦截器),Vue 会去targetMap里找到对应的依赖并执行它们。

  • 时间的变化:
    时间是持续流动的,但它的变化不是由Vue触发的。时钟的每次跳动是操作系统层面的事件,并没有通过 JavaScript 代码去“修改”某个响应式数据(例如执行time.value = newTime)。既然没有set操作,Vue 就不会去遍历依赖列表,自然也就不会更新界面。

一个直观的对比

为了帮你更好地理解,可以看看下面这段代码:

javascript

复制下载

import{ref,watchEffect}from'vue'// 1. 静态时间戳 - 普通 JavaScript 数字conststaticTime=Date.now();// 例如: 1774537416548 (数字类型)// 2. 响应式时间戳 - Vue 的 ref 对象constreactiveTime=ref(Date.now());// 返回 { value: 1774537416548 }// 3. watchEffect - 自动追踪响应式依赖watchEffect(()=>{console.log('静态时间'+staticTime);// 只在初始化时打印一次console.log('响应式时间'+reactiveTime.value)// reactiveTime 变化时重新执行})// 4. 定时器 - 每秒更新响应式时间setInterval(()=>{reactiveTime.value=Date.now();// 更新 ref 的 value 属性},1000)

总结

Date.now()之所以不是响应式依赖,是因为:

  1. 它本身不是响应式数据:它是一个原始值,没有被refreactive包装。
  2. 没有依赖收集:读取它时不会触发Proxyget拦截器,Vue 无法建立依赖关系。
  3. 没有变更通知:时间流逝不会触发Proxyset拦截器,Vue 不知道“数据变了”。

如果你想在Vue中创建一个会随时间自动更新的响应式数据,你需要把它包装成ref,然后手动更新它(例如用setInterval修改ref的值)。这样,Vue的响应式系统就能感知到变化,并更新所有依赖于它的地方。

http://www.jsqmd.com/news/542861/

相关文章:

  • Windows下OpenClaw安装指南:对接ollama GLM-4.7-Flash模型
  • 告别‘无法启动’!VS2019+QT项目打包保姆级教程(含VTK等第三方库处理)
  • TMSpeech:如何用这款Windows神器实现会议摸鱼与高效记录?
  • std::expected
  • Windows触控体验的革命:ThreeFingerDragOnWindows如何重新定义三指拖拽
  • 裂隙注浆模拟:当岩层遇上高粘度浆液
  • Llama-3.2V-11B-cot实操手册:上传JPG/PNG后实时视觉推理全流程
  • LVGL字体扩展避坑指南:freetype缓存管理导致的内存泄漏问题排查实录
  • 基于ViT模型的移动端图像分类应用开发
  • 从VS Code到CLion:跨IDE统一CMake构建命令的最佳实践(含--config参数详解)
  • VMware Unlocker终极指南:如何在Windows和Linux上高效运行macOS虚拟机
  • 第4章 编码规范-4.2 注释规范
  • Qwen3-ASR-0.6B WebUI实战:中文方言自动识别与结果导出操作
  • YOLO-v8.3问题解决:常见报错与GPU配置避坑指南
  • Sonic数字人效果展示:看静态图片如何“开口说话”生成流畅视频
  • 【三维模型+视频】COMSOL 6.2-三维超声辅助激光熔覆案例。 介绍:对于激光熔覆,激光束...
  • 你的CDD文件真的‘干净’吗?深度解析CANoe.Diva自动化测试背后的诊断数据库质量门禁
  • STEP3-VL-10B多场景落地:跨境电商Listing图合规检测(Logo/文字)
  • 节能模式:OpenClaw+nanobot的间歇性任务调度技巧
  • AutoGen Studio作品分享:基于低代码平台构建的智能体团队实战
  • Ubuntu 20.04下rMATS 4.1.2环境配置避坑指南(含GSL 2.5依赖解决方案)
  • Python无GIL时代来了?揭秘CPython 3.13+无锁并发模型的8个高频面试陷阱
  • 为什么你的模型训练慢3.7倍?——深度解析NumPy/PyTorch/JAX张量底层布局差异与迁移避坑清单
  • 告别调试靠猜!用华大单片机串口高效打印调试信息(基于UART0和可变参数函数)
  • c++ 右值引用
  • translategemma-27b-it部署指南:Ollama模型缓存管理与多版本切换实践
  • Onekey终极指南:3分钟快速获取Steam游戏清单的完整解决方案
  • 分享一份2026金三银四Java面试通关宝典!
  • 3大维度解放双手:March7thAssistant让星穹铁道自动化更智能
  • Qwen3-ASR-1.7B司法存证应用:庭审录音自动转写+时间轴对齐(联动aligner)