LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Vue3中的watch和computed

2022/10/24 vue3

a) computed

computed函数用来创造计算属性,和过去一样,它返回的值是一个ref对象。里面可以传方法,或者一个对象,对象中包含set()、get()方法

只读的计算属性:

  姓: <input type="text" v-model="person.firstName" /> <br />
  名: <input type="text" v-model="person.lastName" /> <br />
  <span>全名:{{ person.fullName }}</span>

import { reactive, computed } from "vue";
setup() {
    const person = reactive({
      firstName: "张",
      lastName: "三",
    });

    person.fullName = computed(() => {
      return person.firstName + "-" + person.lastName;
    });

    return { person };
  },

可读可改的计算属性:

   姓: <input type="text" v-model="person.firstName" /> <br />
   名: <input type="text" v-model="person.lastName" /> <br />
   全名:<input type="text" v-model="person.fullName" /> <br />
   import { reactive, computed } from "vue";
     setup() {
    const person = reactive({
      firstName: "张",
      lastName: "三",
    });
    //完整形式
    person.fullName = computed({
      get() {
        return person.firstName + "-" + person.lastName;
      },
      set(value) {
        const nameArr = value.split("-");
        person.firstName = nameArr[0];
        person.lastName = nameArr[1];
      },
    });

    return { person };
  },    
   

b) watch

与vue2中watch配置功能一致

  • 监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)
  • 监视reactive定义的响应式数据中某个属性(对象)时,deep配置有效

1.监视ref定义的一个响应式对象

 let count = ref(0);
    let msg = ref("你好啊");

    const person = reactive({
      name: "张三",
      age: 18,
      jobs: {
        job1: {
          salary: 20,
        },
      },
    });


    watch(count, (newVal, oldVal) => {
      console.log("count的值改变了", newVal, oldVal);
    });

2.监视ref定义的多个响应式对象

   watch(
      [count, msg],
      (newVal, oldVal) => {
        console.log("count或者msg的数据改变了", newVal, oldVal);
      },
      { immediate: true, deep: true }
    );
    
    

3.监视reactive定义的响应式对象的全部属性,但是这里无法正确的获取oldVal,强制开启了深度监视(deep配置无效)

  watch(person, (newVal, oldVal) => {
      console.log("person改变了", newVal, oldVal);
    },{deep:false});//deep配置无效

4.监视reactive定义的响应式数据的一个属性,这种方式可获取oldVal

 watch(
      () => person.age,
      (newVal, oldVal) => {
        console.log("person的age改变了", newVal, oldVal);
      }
    );

5.监视reactive定义的响应式数据的某些属性,这种方式可获取oldVal

    watch([() => person.age, () => person.name], (newVal, oldVal) => {
      console.log("person的age或name改变了", newVal, oldVal);
    });

6.特殊情况,监视reactive定义的响应式对象中的某个对象,这里deep配置有效,获取不到oldVal

    watch(
      () => person.jobs,
      (newVal, oldVal) => {
        console.log("person的job改变了", newVal, oldVal);
      },
      { deep: true }
    );

7.ref定义的响应式对象

如果定义的对象是基本数据,则不用加.value,例如 let count=ref(0),如果在监视时加了.value,则监视的则是0这个数字,不是我们想要的refImpl数据对象,如果定义的对象是一个对象,则需要加.value,否则监听时虽然对象内的数据改变,但整个对象没有改变(其内存地址没有发生变化),会认为没有发生变化,也就监听不到对象内数据的变化

    const person = ref({
      name: "张三",
      age: 18,
      jobs: {
        job1: {
          salary: 20,
        },
      },
    });



1.数据为对象时,需要加.value
 watch(person.value, (newVal, oldVal) => {
      console.log("person的值改变了", newVal, oldVal);
    });
2.若不加.value,则需要深度监听(deep:true),才能监听到对象的改变
    watch(
      person,
      (newVal, oldVal) => {
        console.log("person的值改变了", newVal, oldVal);
      },
      { deep: true }
    );

watch和watchEffect

a) 纯函数与副作用

effect全称叫side effect,副作用。

什么是副作用呢,一个函数运行后产生了可以影响其外部或可以看到的效果,就叫副作用,比如document. Body. Append,alert再或者是showModel(在页面中展示一个弹层),或者window.open打开一个新窗口。
Snipaste_2021-11-23_10-58-18

// 纯函数:如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。它不应修改程序的状态或引起副作用。
function priceAfterTax(productPrice) { 
    return (productPrice * 0.20) + productPrice;
}

// 副作用:一个可以被观察的副作用是在函数内部与其外部的任意交互。这可能是在函数内修改外部的变量,或者在函数里调用另外一个函数等。
var tax = 20;
function calculateTax(productPrice) {
    tax = tax/100
    return (productPrice * tax) + productPrice;
}
  • react中副作用可以这样理解:组件初始化的时候,组件根据开发者的设定,由自身驱动的第一次DOM修改,就是主作用。主作用之后,组件开始执行用户逻辑,这时你眼里的业务逻辑代码,在React眼里都是副作用。

  • vue3中的副作用与react中副作用的定义类似,响应式数据的变更造成的其他连锁反应,以及后续逻辑,这些连锁反应都叫副作用。

哪些函数没有副作用呢,只用来计算结果的函数,比如Math.max,JSON.parse,它们的运行除了返回结果外不会有其它效果,这就叫不产生副作用。

基本上可以简化的理解为副作用就是执行某种操作(副作用函数),无副作用就是执行某种计算(纯函数)。

这里watchEffect的意思就是在观察(watch)到变化后执行一些操作(effect)。

b) watchEffect的基本使用

import { watchEffect, ref } from 'vue'
setup () {
    const userID = ref(0)
    
    watchEffect(() => console.log(userID))
    
    setTimeout(() => {
      userID.value = 1
    }, 1000)

    return {
      userID
    }
 }

c) watch和watchEffect的异同

watch和watchEffect都可以侦听副作用,区别在于:

  • 第一点我们可以从示例代码中看到 watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行;而 watch 只能监听指定的属性而做出变更。
  • 第二点就是 watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。
  • 第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行;而 watch 不需要,因为他一开始就指定了依赖。

d) watchEffect的刷新时机

watchEffect会在所有的组件 update 执行。

<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  setup() {
    const count = ref(0)

    watchEffect(() => {
      console.log(count.value)
    })

    return {
      count
    }
  }
}
</script>

在这个例子中:

  • count 会在初始运行时同步打印出来,因为watchEffect会在组件初始化的时候默认收集一次依赖
  • 更改 count 时,将在组件更新前执行副作用。

如果需要在组件更新(例如:当与模板引用一起)重新运行侦听器副作用,我们可以传递带有 flush 选项的附加 options 对象 (默认为 'pre'):

watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post'
  }
)

e) 停止侦听

watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

在一些情况下,也可以显式调用返回值以停止侦听:

const stop = watchEffect(() => {
  /* ... */
})

stop()

f) 清除副作用的函数

watchEffect函数的onInvalidate方法就是用来清除副作用的,但副作用不一定是不被需要的。它可以是获取数据、事件监听或订阅、改变应用状态、修改 DOM、输出日志等等。清除副作用实际上是Vue3提供给用户的一种取消异步副作用的实现方法。

<template>
  <div>
    <div>{{count}}</div>
    <button @click="doAdd">点我+1</button>
  </div>
</template>

<script>
import { ref, watchEffect, watch } from "vue";

export default {
  setup() {
    const count = ref(1);

    const doAdd = ()=>{
      count.value++
    }

    watchEffect(onInvalidate => {
      console.log(count.value)
      // 异步api调用,返回一个操作对象
      const timer = setInterval(() => {
        console.log("timer执行了")
      }, 1000);

      //onInvalidate(fn)传入的回调会在watchEffect重新运行或者watchEffect停止的时候执行。
      onInvalidate(()=>{
        clearInterval(timer)
      })
    });

    return {
      count,
      doAdd
    };
  }
};
</script>

img_show