◆

Vue computed 与 watch 的区别及 watchEffect 用法

2023-02-02 01:370

计算属性 Computed :

  • 支持缓存,只有依赖数据(vm中 data 的属性)发生改变,才会重新进行计算
    • 缓存的作用

      如下所示,computed 与 method 都对 data 中的数据进行了再一次的处理,都能达到相同的效果。 但是不同的是计算属性是基于它们的反应依赖关系缓存的。 计算属性只在相关响应式依赖发生改变时它们才会重新求值。 这就意味着只要 author.books 还没有发生改变, 多次访问 calculateBooksMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。 我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 list,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 list。如果没有缓存, 我们将不可避免的多次执行 list 的 getter!如果你不希望有缓存,请用 method 来替代。

computed: {
  // 计算属性的 getter
  calculateBooksMessage();
  {
    // `this` 指向 vm 实例
    return this.author.books.length > 0 ? 'Yes' : 'No';
  }
}

methods: {
  calculateBooksMessage();
  {
    return this.author.books.length > 0 ? 'Yes' : 'No';
  }
}
  • 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
  • computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 中声明过的数据通过计算得到的
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用 computed
  • 如果 computed 属性属性值是函数,那么默认会走 get 方法;函数的返回值就是属性的属性值;在 computed 中的,属性都有一个 get 和一个 set 方法,当数据变化时,调用 set 方法。
  • 当模板中的数据需要其他逻辑处理时可以用 computed 处理(对于任何包含响应式数据的复杂逻辑,都应该使用计算属性)
  • 使用场景:当一个值受多个属性影响的时候,

监听属性 watch:

  • 不支持缓存,数据变化,直接会触发相应的操作;
  • watch 支持异步;
  • 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  • 当一个属性发生变化时,需要执行对应的操作;一对多;
  • watch 只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch 想要监听引用类型数据的变化,需要进行深度监听。 "obj.name"(){}------如果 obj 的属性太多,这种方法的效率很低,obj:{handler(newVal){},deep:true}------用 handler+deep 的方式进行深度监听。
  • 使用场景:当一条数据的更改影响到多条数据的时候

区别:

功能上:computed 是计算属性,watch 是监听一个值的变化,然后执行对应的回调。

  • 是否调用缓存:computed 中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而 watch 在每次监听的值发生变化的时候都会执行回调。
  • 是否调用 return:computed 中的函数必须要用 return 返回,watch 中的函数不是必须要用 return。
  • 使用场景:
    • computed----当一个属性受多个属性影响的时候,使用 computed-------购物车商品结算。
    • watch----当一条数据影响多条数据的时候,使用 watch-------搜索框。

vue2 中的 computed

<template>
  苹果数量:
  <input v-model="count.numberOfApples" />
  <br />
  西瓜数量:
  <input v-model="count.numberOfWatermelons" />
  <br />
  鸡蛋数量:
  <input v-model="count.numberOfEggs" />
  <br />
  总数量:
  <input v-model="sum" />
  <br />
  苹果数量 + 西瓜数量:
  <input v-model="sumSecond" />
  <br />
</template>

<script lang="ts">
export default {
  data() {
    return {
      count: {
        numberOfApples: 0,
        numberOfWatermelons: 0,
        numberOfEggs: 0,
      },
    };
  },

  mounted() {
    // this.sumSecond = 10;
    // console.log(this.sumSecond);
  },

  computed: {
    // 计算属性的 getter
    sum() {
      return (
        Number(this.count.numberOfApples) +
        Number(this.count.numberOfWatermelons) +
        Number(this.count.numberOfEggs)
      );
    },

    sumSecond: {
      get() {
        // 当模板中 sumSecond 没有被依赖时,即使 data 里的值改变了,也不会触发 get 函数
        console.log('sumSecond 的 get 触发');
        return (
          Number(this.count.numberOfApples) +
          Number(this.count.numberOfWatermelons)
        );
      },

      set(val) {
        // 当 sumSecond 变化时主动出发 set 函数
        console.log('sumSecond 的 set 触发', val);
      },
    },
  },
};
</script>

vue3 中的 computed

<template>
  苹果数量:
  <input v-model="count.numberOfApples" />
  <br />
  西瓜数量:
  <input v-model="count.numberOfWatermelons" />
  <br />
  鸡蛋数量:
  <input v-model="count.numberOfEggs" />
  <br />
  总数量:
  <input v-model="sum" />
  <br />
  苹果数量 + 西瓜数量:
  <input v-model="sumSecond" />
  <br />
</template>

<script lang="ts">
import { computed, defineComponent, reactive } from 'vue';

export default defineComponent({
  setup(prop) {
    const count: any = reactive({
      numberOfApples: 0,
      numberOfWatermelons: 0,
      numberOfEggs: 0,
    });

    const sum = computed(() => {
      return (
        Number(count.numberOfApples) +
        Number(count.numberOfWatermelons) +
        Number(count.numberOfEggs)
      );
    });

    const sumSecond = computed(() => {
      return Number(count.numberOfApples) + Number(count.numberOfWatermelons);
    });

    return {
      sumSecond,
      sum,
      count,
    };
  },
});
</script>

vue2 中的 watch

<template>
  <div>
    <input type="text" v-model="watchTextOne" />
    <br />
    <br />
    <input type="text" v-model="watchTextTwo" />
    <br />
    <br />
    <input type="text" v-model="watchTextThree" />
    <br />
    <br />
    <input type="text" v-model="watchTextFour.name" />
    <br />
    <br />
    <input type="text" v-model="watchTextFour.age" />
    <br />
    <br />
    <input type="text" v-model="watchTextFour.feature" />
    <br />
    <br />
    <input type="text" v-model="watchTextFour.feature[0]" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      watchTextOne: '',
      watchTextTwo: '',
      watchTextThree: '',
      watchTextFour: {
        name: 'fx',
        age: 18,
        feature: ['smart', 'great'],
      },
    };
  },

  watch: {
    // 第一种watch监听方式
    watchTextOne(newVal, oldVal) {
      console.log('watchTextOne新值:', newVal);
      console.log('watchTextOne旧值:', oldVal);
      this.watchTextTwo = newVal;
      this.watchTextThree = oldVal;
    },

    /*
     注意到handler了吗,我们给 firstName 绑定了一个handler方法,之前我们写的 watch 方法其实默认写的就是这个handler,Vue.js会去处理这个逻辑,最终编译出来其实就是这个handler。
     而immediate:true代表如果在 wacth 里声明了 firstName 之后,就会立即先去执行里面的handler方法,如果为 false就跟我们以前的效果一样,不会在绑定的时候就执行。
     */
    watchTextThree: {
      handler(newVal, oldVal) {
        console.log('watchTextThree新值:', newVal);
        console.log('watchTextThree旧值:', oldVal);
      },

      immediate: true, // immediate表示在watch中首次绑定的时候,是否执行handler
    },

    watchTextFour: {
      handler(newVal, oldVal) {
        console.log('watchTextFour新值:', newVal);
        console.log('watchTextFour旧值:', oldVal);
      },

      deep: true, // 此时会给watchTextFour的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。
    },

    'watchTextFour.age': {
      handler(newVal, oldVal) {
        console.log('watchTextFour.age新值:', newVal);
        console.log('watchTextFour.age旧值:', oldVal);
      },

      deep: true,
    },
  },
};
</script>

vue3 中的 watch

  • watchEffect 函数
    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
    • 监视数据发生变化时回调
    • watchEffect 拿不到新值与旧值(更新前的值)
<template>
  <div>
    watchTextOne:
    <input type="text" v-model="watchTextOne" />
    <br />
    <br />
    watchTextTwo:
    <input type="text" v-model="watchTextTwo" />
    <br />
    <br />
    watchTextThree:
    <input type="text" v-model="watchTextThree" />
    <br />
    <br />
    watchTextSum:
    <input type="text" v-model="watchTextSum" />
    <br />
    <br />
    watchTextFour.name:
    <input type="text" v-model="watchTextFour.name" />
    <br />
    <br />
    watchTextFour.age:
    <input type="text" v-model="watchTextFour.age" />
    <br />
    <br />
    watchTextFour.feature:
    <input type="text" v-model="watchTextFour.feature" />
    <br />
    <br />
    watchTextFour.feature[0]:
    <input type="text" v-model="watchTextFour.feature[0]" />
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref, watch, watchEffect } from 'vue';

export default defineComponent({
  setup(prop) {
    const watchTextOne = ref(2),
      watchTextTwo = ref(1),
      watchTextThree = ref(3),
      watchTextSum = ref(0),
      watchTextFour = reactive({
        name: 'fx',
        age: 18,
        feature: ['smart', 'great'],
      });

    watch(
      watchTextTwo,
      () => {
        watchTextOne.value = watchTextTwo.value + watchTextThree.value;
      },
      {
        immediate: true, // 是否初始化立即执行一次, 默认是false
        deep: true, // 是否是深度监视, 默认是false
      },
    );

    watch([() => watchTextFour.age, () => watchTextFour.name], values => {
      console.log('监视多个数据', values);
    });

    // 可实现类似 computer 一个值受多个值影响的效果
    watchEffect(() => {
      watchTextSum.value =
        Number(watchTextOne.value) +
        Number(watchTextTwo.value) +
        Number(watchTextThree.value);
    });

    return {
      watchTextOne,
      watchTextTwo,
      watchTextThree,
      watchTextFour,
      watchTextSum,
    };
  },
});
</script>

计算属性 vs 侦听器

Vue 提供了一种更通用的方式来观察和响应当前活动的实例上的数据变动:**侦听属性 **。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar',
      fullName: 'Foo Bar',
    };
  },
  watch: {
    firstName(val) {
      this.fullName = val + ' ' + this.lastName;
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val;
    },
  },
}).mount('#demo');

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar',
    };
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    },
  },
}).mount('#demo');

watchEffect

如果有多个负效应,不要粘合在一起,建议写多个 watchEffect。

watchEffect(() => {
  setTimeout(() => console.log(a.val + 1), 1000);
  setTimeout(() => console.log(b.val + 1), 1000);
});

这两个 setTimeout 是两个不相关的效应,不需要同时监听 a 和 b,分开写吧:

watchEffect(() => {
  setTimeout(() => console.log(a.val + 1), 1000);
});

watchEffect(() => {
  setTimeout(() => console.log(b.val + 1), 1000);
});
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue computed 与 watch 的区别及 watchEffect 用法加载中