前言:哈喽,大家好,我是前端菜鸟的自我修养!今天给大家分享【深入剖析watch、computed、watchEffect的区别】,并提供具体代码帮助大家深入理解,彻底掌握!原创不易,如果能帮助到带大家,欢迎 收藏+关注 哦 💕
🌈🌈文章目录
在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更为复杂,它依赖于getter和setter的机制。在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从基础到起飞
🌈 若有帮助,还请 关注➕点赞➕收藏 ,不行的话我再努努力💪💪💪
更多专栏订阅推荐:
👍 前端工程搭建
💕 前端常见问题汇总,避坑大全✍️ GIS地图与大数据可视化