[vue2/vue3] 详细剖析watch、computed、watchEffect的区别,原理解读

前言:哈喽,大家好,我是前端菜鸟的自我修养!今天给大家分享【深入剖析watch、computed、watchEffect的区别】,并提供具体代码帮助大家深入理解,彻底掌握!原创不易,如果能帮助到带大家,欢迎 收藏+关注 哦 💕

🌈🌈文章目录

一、watch

1.使用语法

2.watch的实现原理 

二、computed

1.简单使用

2.computed的实现原理

三、watchEffect

1.简单使用

2.小结

3.watchEffect的实现原理

4.注意事项

1. 避免过度监听

2. 异步操作需谨慎处理

3. 避免无限循环

四、实际开发当中该怎么去选择

 1.watch

1.1 异步操作触发

1.2 深度监听

2.computed

2.1 派生数据 

2.2 性能优化

3.watchEffect

3.1 自动依赖追踪

3.2 动态数据处理

五、总结

在Vue中,数据响应式是一个核心概念,它使得当数据变化时,相关的视图会自动更新。为了更灵活地处理数据的变化,Vue提供了多种方式,其中包括watch、computed和watchEffect

一句话概括watch ​适用于需要有条件地监听数据变化的场景,computed​ 适用于创建派生数据和性能优化,而watchEffect 适用于自动追踪依赖的场景。在实际应用中,根据具体需求选择合适的API可以更好地发挥Vue的响应式能力。

一、watch

1.使用语法

watch是Vue中一个非常强大的特性,它允许你监听数据的变化并做出相应的反应。它有两种用法:一是监听一个具体的数据变化,二是监听多个数据的变化。

// 监听单个数据
watch('someData', (newVal, oldVal) => {
  // 做一些事情
});

// 监听多个数据
watch(['data1', 'data2'], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
  // 做一些事情
});

2.watch的实现原理 

Vue中watch的实现主要依赖于Watcher这个核心类。当调用watch时,实际上是创建了一个Watcher实例,并将回调函数和需要监听的数据传递给这个实例。

// 简化版的watch实现
function watch(source, cb) {
  const watcher = new Watcher(source, cb);
}

Watcher类的构造函数接收两个参数,分别是需要监听的数据(可以是一个字符串,也可以是一个返回值的函数)回调函数。在构造函数中,会对数据进行求值,然后将这个Watcher实例添加到数据的依赖列表中。

class Watcher {
  constructor(source, cb) {
    this.getter = typeof source === 'function' ? source : () => this.vm[source];
    this.cb = cb;
    this.value = this.get();
  }

  get() {
    pushTarget(this); // 将当前Watcher实例压栈
    const value = this.getter.call(this.vm); // 触发数据的getter,将当前Watcher实例添加到依赖列表中
    popTarget(); // 将当前Watcher实例出栈
    return value;
  }

  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb(this.value, oldValue);
  }
}

 简单来说,watch的实现原理就是通过Watcher实例来监听数据的变化,当数据变化时,触发update方法执行回调函数

二、computed

1.简单使用

computed用于计算派生数据。它依赖于其他响应式数据,并且只有在相关数据发生变化时才会重新计算

computed(() => {
  return someData * 2;
});

2.computed的实现原理

computed的实现原理相对于watch更为复杂,它依赖于gettersetter的机制。在Vue中,computed的定义是一个包含get和set方法的对象

const computed = {
  get() {
    return someData * 2;
  },
  set(value) {
    someData = value / 2;
  }
};

在computed的实现中,当访问计算属性时,实际上是执行了get方法,而在数据变化时,会执行set方法。这里主要使用了Object.defineProperty这个JavaScript的特性。

function createComputedGetter() {
  return function computedGetter() {
    const value = getter.call(this); // 执行计算属性的get方法
    track(target, TrackOpTypes.GET, 'value'); // 添加依赖
    return value;
  };
}

function createComputedSetter() {
  return function computedSetter(newValue) {
    setter.call(this, newValue); // 执行计算属性的set方法
    trigger(target, TriggerOpTypes.SET, 'value'); // 触发更新
  };
}

function computed(getterOrOptions) {
  const getter = 
    typeof getterOrOptions === 'function'
      ? getterOrOptions
      : getterOrOptions.get;

  const setter = getterOrOptions.set;

  const cRef = new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.get
  );

  return cRef;
}

class ComputedRefImpl {
  // 构造函数
  constructor(getter, setter, isReadonly) {
    // ...
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true;
          triggerRef(this);
        }
      },
    });
  }
  // ...
}

在上述代码中,createComputedGetter和createComputedSetter用于创建计算属性的getter和setter。computed函数接收一个getter函数,并通过Object.defineProperty将getter和setter添加到计算属性的引用对象中。

当计算属性被访问时,会触发getter,此时会将当前计算属性添加到依赖列表中。当计算属性的依赖数据发生变化时,会触发setter,并通过triggerRef触发计算属性的更新。

三、watchEffect

1.简单使用

watchEffect()函数用于创建一个自动追踪依赖的响应式副作用。它会在初始化时会立即执行一次,并自动追踪在回调函数中使用的所有响应式数据,在这些数据发生变化时重新运行回调函数。

例如,在每当 todoId 的引用发生变化时使用侦听器来加载一个远程资源,如果用watch,是这么写:

<template>
  <div>Test</div>
</template>
 
<script setup>
  import { ref, watch } from 'vue'
  const todoId = ref(1)
  const data = ref(null)
 
  watch(
    todoId,
    async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
      data.value = await response.json()
      console.log(data.value)
    },
    { immediate: true }
  )
</script>

打印:

但是用了watchEffect()就可以简化为:

<template>
  <div>Test</div>
</template>
 
<script setup>
  import { ref, watchEffect } from 'vue'
  const todoId = ref(1)
  const data = ref(null)
 
  watchEffect(async () => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
    data.value = await response.json()
    console.log(data.value)
  })
</script>

打印:

2.小结

两者都立即打印了data。但下面的例子中,回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。

从这个角度来看,watchEffect()的作用类似于Vue2中的computed,即依赖项发生变化,自己也跟着改变。但与computed不同,watchEffect()没有返回值,而是直接执行回调函数

3.watchEffect的实现原理

watchEffect是Vue3中引入的响应式API,它用于执行一个响应式函数,并在函数中响应式地追踪其依赖。与watch不同,watchEffect不需要显式地指定依赖,它会自动追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行

首先,watchEffect的核心是依赖追踪和触发。Vue3中的响应式系统使用ReactiveEffect类来表示一个响应式的函数。

class ReactiveEffect {
  constructor(fn, scheduler = null) {
    // ...
    this.deps = [];
    this.scheduler = scheduler;
  }

  run() {
    // 执行响应式函数
    this.active && this.getter();
  }

  stop() {
    // 停止追踪
    cleanupEffect(this);
  }
}

export function watchEffect(effect, options = {}) {
  // 创建ReactiveEffect实例
  const runner = effect;
  const job = () => {
    if (!runner.active) {
      return;
    }
    if (cleanup) {
      cleanup();
    }
    // 执行响应式函数
    return runner.run();
  };
  // 执行响应式函数
  job();
  // 返回停止函数
  return () => {
    stop(runner);
  };
}

在上述代码中,ReactiveEffect类表示一个响应式的函数。watchEffect函数接收一个响应式函数,并创建一个ReactiveEffect实例。在执行时,该实例会追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行。

watchEffect返回一个停止函数,用于停止对响应式数据的追踪

4.注意事项

1. 避免过度监听

由于watchEffect()会追踪使用到的所有响应式数据,因此要确保在回调函数中只使用必要的响应式数据,避免造成不必要的渲染开销。

2. 异步操作需谨慎处理

由于watchEffect()会立即执行回调函数,如果在回调函数中进行异步操作,需要谨慎处理,以免导致意外行为或副作用。

<script setup>
import { watchEffect } from 'vue'
 
// 它会自动停止
watchEffect(() => {})
 
// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>
3. 避免无限循环

当在watchEffect()的回调中修改响应式数据时,可能会导致无限循环。要避免此问题,可以使用watch()函数并设置 immediate: true选项,或者使用ref来存储临时数据。

四、实际开发当中该怎么去选择

 1.watch

watch主要用于监听特定的数据变化并执行回调函数。它可以监听数据的变化,并在满足一定条件时执行相应的操作。常见的使用场景包括:

1.1 异步操作触发

当某个数据发生变化后,需要进行异步操作,比如发起一个网络请求或执行一段耗时的操作。

watch(() => state.data, async (newData, oldData) => {
  // 异步操作
  await fetchData(newData);
});

1.2 深度监听

监听对象或数组的变化,并在深层次的数据变化时执行回调

watch(() => state.user.address.city, (newCity, oldCity) => {
  console.log(`City changed from ${oldCity} to ${newCity}`);
});

2.computed

computed用于创建一个计算属性,它依赖于其他响应式数据,并且只有在依赖数据发生变化时才重新计算。常见的使用场景包括:

2.1 派生数据 

根据现有的数据计算出一些派生的数据,而不必每次都重新计算

const fullName = computed(() => `${state.firstName} ${state.lastName}`);

2.2 性能优化

避免不必要的重复计算,提高性能。

const result = computed(() => {
  // 避免重复计算
  if (someCondition) {
    return heavyCalculation();
  } else {
    return defaultResult;
  }
});

3.watchEffect

watchEffect用于执行一个响应式函数,并在函数内部自动追踪依赖。它适用于不需要显式指定依赖,而是在函数内部自动追踪所有响应式数据变化的场景。常见的使用场景包括:

3.1 自动依赖追踪

函数内部的所有响应式数据都被自动追踪,无需显式指定

watchEffect(() => {
  console.log(`Count changed to ${state.count}`);
});

3.2 动态数据处理

处理动态变化的数据,无需手动管理依赖

watchEffect(() => {
  // 处理动态变化的数据
  handleDynamicData();
})

五、总结

watch ​适用于需要有条件地监听数据变化的场景,computed​ 适用于创建派生数据和性能优化,而watchEffect 适用于自动追踪依赖的场景。在实际应用中,根据具体需求选择合适的API可以更好地发挥Vue的响应式能力。

  好了,本文就到这里吧,点个关注再走嘛~ 

🚀 个人简介:7年开发经验,某大型国企前端负责人,信息系统项目管理师、CSDN优质创作者、阿里云专家博主,分享前端相关技术与工作常见问题~
💟 作    者:前端菜鸟的自我修养❣️
📝 专    栏:vue从基础到起飞
🌈 若有帮助,还请 关注➕点赞➕收藏  ,不行的话我再努努力💪💪💪  

  更多专栏订阅推荐:

👍 前端工程搭建
💕 前端常见问题汇总,避坑大全

📝 javascript深入研究

✍️ GIS地图与大数据可视化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/761926.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安装docker compose与elasticsearch,kibana

1.docker compose安装 1.1是否已安装docker docker -v 1.2安装docker compose curl -SL https://github.com/docker/compose/releases/download/v2.18.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-composeps:如果网络太慢可直接在博客中下载附属文件 下载后修…

缺失d3dx9_43.dll是怎么回事?教你几种靠谱的解决方法

在日常生活和工作中&#xff0c;电脑已经成为我们不可或缺的工具。然而&#xff0c;在使用电脑的过程中&#xff0c;我们常常会遇到一些问题&#xff0c;其中之一就是软件运行时提示d3dx9_43.dll丢失。这个问题会导致软件游戏无法启动运行&#xff0c;但只要我们了解其原因和解…

光扩散微球市场增长空间大 我国已实现其产业化

光扩散微球市场增长空间大 我国已实现其产业化 光扩散微球是一种高性能微球材料&#xff0c;具有优异的光学和力学性能&#xff0c;且不含杂质&#xff0c;将其涂抹在光扩散膜&#xff08;板&#xff09;上&#xff0c;可以将点光源变成面光源&#xff0c;使显示面板的布光更加…

论文阅读YOLO-World: Real-Time Open-Vocabulary Object Detection

核心&#xff1a; 开放词汇的实时的yolo检测器。重参数化的视觉语言聚合路径模块Re-parameterizable VisionLanguage Path Aggregation Network (RepVL-PAN)实时核心&#xff1a;轻量化的检测器离线词汇推理过程重参数化 方法 预训练方案&#xff1a;将实例注释重新定义为区域…

喜讯!安全狗荣获“2023年网络安全技术支撑优秀单位”称号

6月6日&#xff0c;由中共厦门市委网络安全和信息化委员会办公室&#xff08;以下简称“厦门市委网信办”&#xff09;主办的2023年网络安全技术支撑优秀单位颁奖仪式在厦门成功举行。 作为国内云原生安全领导厂商&#xff0c;安全狗受邀出席此次活动。 会上&#xff0c;安全狗…

【ai】ubuntu18.04 找不到 nvcc --version问题

nvcc --version显示command not found问题 这个是cuda 库: windows安装了12.5 : 参考大神:解决nvcc --version显示command not found问题 原文链接:https://blog.csdn.net/Flying_sfeng/article/details/103343813 /usr/local/cuda/lib64 与 /usr/local/cuda-11.3/lib64 完…

OZON家具用品有哪些是热销的

在俄罗斯电商市场中&#xff0c;OZON平台凭借其强大的影响力和广泛的用户基础&#xff0c;成为家具用品销售的重要阵地。那么&#xff0c;在这个平台上&#xff0c;哪些家具用品最受欢迎&#xff0c;销量持续走高呢&#xff1f;本文将为您揭秘OZON家具用品的热销秘诀&#xff0…

Golang开发:构建支持并发的网络爬虫

Golang开发&#xff1a;构建支持并发的网络爬虫 随着互联网的快速发展&#xff0c;获取网络数据成为了许多应用场景中的关键需求。网络爬虫作为一种自动化获取网络数据的工具&#xff0c;也因此迅速崛起。而为了应对日益庞大的网络数据&#xff0c;开发支持并发的爬虫成为了必…

INS-GPS组合导航——卡尔曼滤波

系列文章目录 《SAR笔记-卫星轨道建模》 《SAR笔记-卫星轨迹&#xff08;三维建模&#xff09;》 《常用坐标系》 文章目录 前言 一、经典卡尔曼滤波 二、扩展卡尔曼滤波 三、无迹卡尔曼滤波 总结 前言 SAR成像仪器搭载于运动平台&#xff0c;平台的自定位误差将影响SAR…

20240701每日后端------------java启动JVM参数配置说明Parameters -D, -X, -XX

主题 JVM有很多参数&#xff0c;当我们通过命令行启动Java程序时&#xff08;例如&#xff0c; java -jar app.jar&#xff09; 我们经常指定各种参数选项。很多人对为什么有时我们使用 -D &#xff0c;有时我们使用 -X &#xff0c;偶尔我们使用 -XX 感到困惑。 名词解释 …

08:结构体

结构体 1、为什么需要结构体2、如何定义结构体3、怎么使用结构体变量3.1、赋值和初始化3.2、结构体变量的输出 1、为什么需要结构体 为了表示一些复杂的事物&#xff0c;而普通的基本类型无法满足实际要求。什么叫结构体 把一些基本类型数据组合在一起形成的一个新的数据类型&…

深入剖析Tomcat(十四) Server、Service 组件:如何启停Tomcat服务?

通过前面文章的学习&#xff0c;我们已经了解了连接器&#xff0c;四大容器是如何配合工作的&#xff0c;在源码中提供的示例也都是“一个连接器”“一个顶层容器”的结构。并且启动方式是分别启动连接器和容器&#xff0c;类似下面代码 connector.setContainer(engine); try …

DP V2.1a标准学习

一、说明 DP是DisplayPort的简写,是视频电子标准协会(VESA)标准化的数字式视频接口标准,可用于板内芯片之间的连接,也可用于输出接口连接外部设备。DisplayPort是一种基于数据包的可扩展协议,用于传输视频和音频数据。DisplayPort 具有高度可扩展性,并具有保持向后兼容…

【一步一步了解Java系列】:对这个系列的总结以及对缺漏内部类知识的补充

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 br />个人主页&#xff1a;Gu Gu Study专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xf…

大模型微调新范式:当LoRA遇见MoE

©PaperWeekly 原创 作者 | 陈思硕 单位 | 北京大学 研究方向 | 自然语言处理 图片 当 LoRA 遇见 MoE&#xff0c;会擦出怎样的火花&#xff1f; 图片 ▲ 左侧&#xff1a;原始版本的 LoRA&#xff0c;权重是稠密的&#xff0c;每个样本都会激活所有参数&#xff1b;右…

第二节:如何使用thymeleaf渲染html(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;今天来学习如何使用thymeleaf渲染html。该模板运用不广泛&#xff0c;所以本节内容了解既可。 第一步&#xff1a;创建html文件。 在模板templates目录下创建一个html文件。 编写代码如下&#xff1a; <!DOCTYPE html> <…

Sentinel如何使用BlockExceptionHandler实现限流/降级错误页面显示

1、修改配置项&#xff0c;打开对Spring MVC端点的保护 spring.cloud.sentinel.filter.enabledtrue 2、编写 BlockExceptionHandler的实现类 MyUrlBlockHandler.java package com.codex.terry.sentinel.urlblockhandler;/*** 文件名称: MyUrlBlockHandler.java* 编写人: yh…

tf1问题记录

在复现一个开源项目https://github.com/macanv/BERT-BiLSTM-CRF-NER。有一个疑似TensorFlow-gpu、cudnn、cuda之间版本不兼容的问题。问题详情如下&#xff1a; 在base中输入nvidia-smi显示无此命令&#xff1a; 输入nvitop可正常显示&#xff1a; 输入nvcc -V显示为&#x…

机器学习——强化学习状态值函数V和动作值函数Q的个人思考

最近在回顾《西瓜书》的理论知识&#xff0c;回顾到最后一章——“强化学习”时对于值函数部分有些懵了&#xff0c;所以重新在网上查了一下&#xff0c;发现之前理解的&#xff0c;包括网上的大多数对于值函数的描述都过于学术化、公式化&#xff0c;不太能直观的理解值函数以…

SeeSR: Towards Semantics-Aware Real-World Image Super-Resolution

CVPR2024 香港理工大学&OPPO&bytedancehttps://github.com/cswry/SeeSR?tabreadme-ov-file#-licensehttps://arxiv.org/pdf/2311.16518#page5.80 问题引入 因为有些LR退化情况比较严重&#xff0c;所以超分之后的结果会出现语义的不一致的情况&#xff0c;所以本文训…