在Vue中,数据响应式是一个核心概念,它使得当数据变化时,相关的视图会自动更新。为了更灵活地处理数据的变化,Vue提供了多种方式,其中包括watch、computed和watchEffect。
watch是Vue中一个非常强大的特性,它允许你监听数据的变化并做出相应的反应。它有两种用法:一是监听一个具体的数据变化,二是监听多个数据的变化。
// 监听单个数据watch('someData',(newVal,oldVal)=>{// 做一些事情});// 监听多个数据watch(['data1','data2'],([newVal1,newVal2],[oldVal1,oldVal2])=>{// 做一些事情});
Vue中watch的实现主要依赖于Watcher这个核心类。当调用watch时,实际上是创建了一个Watcher实例,并将回调函数和需要监听的数据传递给这个实例。
// 简化版的watch实现functionwatch(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实例压栈constvalue=this.getter.call(this.vm);// 触发数据的getter,将当前Watcher实例添加到依赖列表中popTarget();// 将当前Watcher实例出栈returnvalue;}update(){ const oldValue=this.value;this.value=this.get();this.cb(this.value,oldValue);} }
简单来说,watch的实现原理就是通过Watcher实例来监听数据的变化,当数据变化时,触发update方法执行回调函数。
computed用于计算派生数据。它依赖于其他响应式数据,并且只有在相关数据发生变化时才会重新计算。
computed(()=>{returnsomeData*2;});
computed的实现原理相对于watch更为复杂,它依赖于getter和setter的机制。在Vue中,computed的定义是一个包含get和set方法的对象。
const computed={ get(){returnsomeData*2;},set(value){ someData=value/2;} };
在computed的实现中,当访问计算属性时,实际上是执行了get方法,而在数据变化时,会执行set方法。这里主要使用了Object.defineProperty这个JavaScript的特性。
functioncreateComputedGetter(){returnfunctioncomputedGetter(){ constvalue=getter.call(this);// 执行计算属性的get方法track(target,TrackOpTypes.GET,'value');// 添加依赖returnvalue;};}functioncreateComputedSetter(){returnfunctioncomputedSetter(newValue){ setter.call(this,newValue);// 执行计算属性的set方法trigger(target,TriggerOpTypes.SET,'value');// 触发更新};}functioncomputed(getterOrOptions){ const getter=typeof getterOrOptions==='function'? getterOrOptions : getterOrOptions.get;const setter=getterOrOptions.set;const cRef=new ComputedRefImpl(getter,setter,isFunction(getterOrOptions)||!getterOrOptions.get);returncRef;} 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是Vue 3新增的特性,它用于监听一个函数内部的响应式数据变化,当变化时,函数会被重新执行。
watchEffect(()=>{// 依赖于响应式数据的操作});
watchEffect是Vue 3中引入的响应式API,它用于执行一个响应式函数,并在函数中响应式地追踪其依赖。与watch不同,watchEffect不需要显式地指定依赖,它会自动追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行。
以下是watchEffect的简单用法:
import{ watchEffect,reactive }from'vue';const state=reactive({ count:0,});watchEffect(()=>{ console.log(state.count);});
在这个例子中,watchEffect内部的函数会自动追踪state.count的变化,并在其变化时触发函数执行。
现在,让我们来探讨watchEffect的实现原理。
首先,watchEffect的核心是依赖追踪和触发。Vue 3中的响应式系统使用ReactiveEffect类来表示一个响应式的函数。
class ReactiveEffect { constructor(fn,scheduler=null){// ...this.deps=[];this.scheduler=scheduler;} run(){// 执行响应式函数this.active&&this.getter();} stop(){// 停止追踪cleanupEffect(this);} } exportfunctionwatchEffect(effect,options={}){// 创建ReactiveEffect实例const runner=effect;const job=()=>{if(!runner.active){return;}if(cleanup){ cleanup();}// 执行响应式函数returnrunner.run();};// 执行响应式函数job();// 返回停止函数return()=>{ stop(runner);};}
在上述代码中,ReactiveEffect类表示一个响应式的函数。watchEffect函数接收一个响应式函数,并创建一个ReactiveEffect实例。在执行时,该实例会追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行。
watchEffect返回一个停止函数,用于停止对响应式数据的追踪。
watch主要用于监听特定的数据变化并执行回调函数。它可以监听数据的变化,并在满足一定条件时执行相应的操作。常见的使用场景包括:
异步操作触发:当某个数据发生变化后,需要进行异步操作,比如发起一个网络请求或执行一段耗时的操作。
watch(()=>state.data,async(newData,oldData)=>{// 异步操作await fetchData(newData);});
深度监听:监听对象或数组的变化,并在深层次的数据变化时执行回调。
watch(()=>state.user.address.city,(newCity,oldCity)=>{ console.log(`City changed from ${oldCity} to ${newCity}`);});
computed用于创建一个计算属性,它依赖于其他响应式数据,并且只有在依赖数据发生变化时才重新计算。常见的使用场景包括:
派生数据:根据现有的数据计算出一些派生的数据,而不必每次都重新计算。
const fullName = computed(() => `${state.firstName} ${state.lastName}`);
性能优化:避免不必要的重复计算,提高性能。
const result=computed(()=>{// 避免重复计算if(someCondition){returnheavyCalculation();}else{returndefaultResult;} });
watchEffect用于执行一个响应式函数,并在函数内部自动追踪依赖。它适用于不需要显式指定依赖,而是在函数内部自动追踪所有响应式数据变化的场景。常见的使用场景包括:
自动依赖追踪:函数内部的所有响应式数据都被自动追踪,无需显式指定。
watchEffect(()=>{ console.log(`Count changed to ${state.count}`);});
动态数据处理:处理动态变化的数据,无需手动管理依赖。
watchEffect(()=>{// 处理动态变化的数据handleDynamicData();});
总体而言,watch适用于需要有条件地监听数据变化的场景,computed适用于创建派生数据和性能优化,而watchEffect适用于自动追踪依赖的场景。在实际应用中,根据具体需求选择合适的API可以更好地发挥Vue的响应式能力。