在Vue中,数据呼应式是一个核心概念,它使得当数据改变时,相关的视图会主动更新。为了更灵活地处理数据的改变,Vue供给了多种方式,其中包含watchcomputedwatchEffect

watch

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

// 监听单个数据
watch('someData', (newVal, oldVal) => {
  // 做一些工作
});
// 监听多个数据
watch(['data1', 'data2'], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
  // 做一些工作
});

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

computed用于核算派生数据。它依靠于其他呼应式数据,而且只要在相关数据发生改变时才会从头核算。

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

computed的完成原理

computed的完成原理相对于watch更为复杂,它依靠于gettersetter的机制。在Vue中,computed的界说是一个包含getset办法的目标。

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);
        }
      },
    });
  }
  // ...
}

在上述代码中,createComputedGettercreateComputedSetter用于创立核算特点的gettersettercomputed函数接纳一个getter函数,并经过Object.definePropertygettersetter添加到核算特点的引用目标中。

当核算特点被拜访时,会触发getter,此时会将当时核算特点添加到依靠列表中。当核算特点的依靠数据发生改变时,会触发setter,并经过triggerRef触发核算特点的更新。

watchEffect

watchEffect是Vue 3新增的特性,它用于监听一个函数内部的呼应式数据改变,当改变时,函数会被从头履行。

watchEffect(() => {
  // 依靠于呼应式数据的操作
});

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);
  }
}
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回来一个中止函数,用于中止对呼应式数据的追寻。

实践开发当中该怎么去选择

watch

watch首要用于监听特定的数据改变并履行回调函数。它可以监听数据的改变,并在满意一定条件时履行相应的操作。常见的运用场景包含:

  1. 异步操作触发: 当某个数据发生改变后,需求进行异步操作,比方发起一个网络恳求或履行一段耗时的操作。

    watch(() => state.data, async (newData, oldData) => {
      // 异步操作
      await fetchData(newData);
    });
    
  2. 深度监听: 监听目标或数组的改变,并在深层次的数据改变时履行回调。

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

computed

computed用于创立一个核算特点,它依靠于其他呼应式数据,而且只要在依靠数据发生改变时才从头核算。常见的运用场景包含:

  1. 派生数据: 依据现有的数据核算出一些派生的数据,而不必每次都从头核算。

    const fullName = computed(() => `${state.firstName} ${state.lastName}`);
    
  2. 功能优化: 防止不必要的重复核算,进步功能。

    const result = computed(() => {
      // 防止重复核算
      if (someCondition) {
        return heavyCalculation();
      } else {
        return defaultResult;
      }
    });
    

watchEffect

watchEffect用于履行一个呼应式函数,并在函数内部主动追寻依靠。它适用于不需求显式指定依靠,而是在函数内部主动追寻一切呼应式数据改变的场景。常见的运用场景包含:

  1. 主动依靠追寻: 函数内部的一切呼应式数据都被主动追寻,无需显式指定。

    watchEffect(() => {
      console.log(`Count changed to ${state.count}`);
    });
    
  2. 动态数据处理 处理动态改变的数据,无需手动办理依靠。

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

总体而言,watch适用于需求有条件地监听数据改变的场景,computed适用于创立派生数据和功能优化,而watchEffect适用于主动追寻依靠的场景。在实践应用中,依据具体需求选择合适的API可以更好地发挥Vue的呼应式才能。