前言:哈喽,大家好,我是前端菜鸟的自我修养!今天给大家分享【深入剖析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更为复杂,它依赖于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从基础到起飞
🌈 若有帮助,还请 关注➕点赞➕收藏 ,不行的话我再努努力💪💪💪
更多专栏订阅推荐:
👍 前端工程搭建
💕 前端常见问题汇总,避坑大全📝 javascript深入研究
✍️ GIS地图与大数据可视化