computed

  • 在组合式 API 中,计算属性 computed 是一个函数
  1. 完整写法 - 接收一个 [配置对象] 作为参数: computed( { get(){ return X }, set(val){ } } )
    配置对象有 2 个配置项:getter、setter;
    getter:获取当前计算属性时触发;返回一个 ref 对象;
    setter:修改当前计算属性时触发;新值会作为参数传入;
    computed 会自动追踪响应式依赖,监听的属性更新后 计算属性也会更新。
  • 需要注意:[getter 里返回的数据的类型] 要与 [setter 里接收的参数的类型] 一样!!!
<template>
  <p>firstName: <input type="text" v-model="person.firstName" /></p>
  <p>lastName: <input type="text" v-model="person.lastName" /></p>
  <p>fullName: <input type="text" v-model="person.fullName" /></p>
</template>
<script setup lang="ts">
import { reactive, computed } from "vue";
import type { WritableComputedRef } from "vue";
interface Person {
  firstName: string;
  lastName: string;
  fullName?: WritableComputedRef<string>;
let person: Person = reactive({ firstName: "super", lastName: "man" });
person.fullName = computed({
  // getter
  get: () => person.firstName + "-" + person.lastName,
  // setter
  set(val) {
    let temp = val.split("-");
    person.firstName = temp[0];
    person.lastName = temp[1];
});
</script>
  1. 如果只设置 computed 的 getter,可以简写 - computed 函数接收一个 [回调函数] 作为参数:computed(callback)
<template>
  <p>firstName: <input type="text" v-model="person.firstName" /></p>
  <p>lastName: <input type="text" v-model="person.lastName" /></p>
  <p>fullName: {{ person.fullName }}</p>
</template>
<script setup lang="ts">
import { reactive, computed } from "vue";
import type { WritableComputedRef } from "vue";
interface Person {
  firstName: string;
  lastName: string;
  fullName?: WritableComputedRef<string>;
let person: Person = reactive({ firstName: "super", lastName: "man" });
person.fullName = computed(() => person.firstName + "-" + person.lastName);
</script>
  1. computed() 会自动从其计算函数的返回值上推导出类型
  2. 也可以通过 [泛型参数] 显式指定类型
person.fullName = computed<string>(() => person.firstName + "-" + person.lastName);
 

计算属性 vs 方法

计算属性能实现的 函数也能实现。不同的是,计算属性的值会根据其响应性依赖进行缓存。一个计算属性只会在其响应性依赖更新时才重新执行,只要其响应性依赖不更新,获取的就是缓存值。而函数会在每次获取数据时都执行一次。

  1. 计算属性的计算函数只做计算,不应有其他副作用
  2. 避免直接修改计算属性,计算属性的返回值应该被视为只读的

watch

  • 在组合式 API 中,watch 是一个函数,接收 3 个参数:① 监听的数据、 ② 回调函数、 ③ 配置对象 (可选)

基本类型数据

监听 ref 定义的基本类型数据:watch( 变量名, (oldVal, newVal) => {}[, 配置对象] )

<template>
    <p>name: {{ name }}</p>
    <button @click="name += '~'">点击修改 name</button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const name = ref('superman');
watch(
    name,
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log('newVal', newVal);
    { immediate: true } // 强制侦听器的回调在组件加载完毕后立即执行一次
</script>

引用类型数据

  • 会强制深度监听(此时,即使设置了 deep: false,也还是会深度监听)
  • oldVal 会与 newVal 一样,因为它们是同一个对象(引用类型数据操作的是内存地址)
  1. 监听 reactive 定义的引用类型数据:watch( 变量名, (oldVal, newVal) => {}[, 配置对象] )
<template>
    <p>msg.job.j1.salary: {{ msg.job.j1.salary }}</p>
    <button @click="msg.job.j1.salary++">点击修改</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const msg = reactive({ job: { j1: { salary: 20 } } });
watch(
    msg,
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log(oldVal === newVal); // true
    { deep: false } /* 会强制深度监听, deep: false 无效 */
</script>
  1. 监听 ref 定义的引用类型数据:watch( 变量名.value, (oldVal, newVal) => {}[, 配置对象] )
import { ref, watch } from 'vue';
const msg = ref({ job: { j1: { salary: 20 } } });
watch(msg.value /* 需要使用 `.value` 指定 ref 定义的引用类型数据 */, (oldVal, newVal) => {
    console.log('oldVal', oldVal);
    console.log(oldVal === newVal);
});

对象的基本类型属性

  • 需要通过 [getter 函数] 指定监听的属性(如果直接写监听的属性,会抛出警告)
  1. 监听 reactive 对象的基本类型的属性:watch( () => 变量名.属性名, (oldVal, newVal) => {}[, 配置对象] )
<template>
    <p>msg.age: {{ msg.age }}</p>
    <button @click="msg.age++">点击修改</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
let msg = reactive({ age: 21 });
watch(
    () => msg.age,
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log('newVal', newVal);
</script>
  1. 监听 ref 对象的基本类型属性:watch( () => 变量名.value.属性名, (oldVal, newVal) => {}[, 配置对象] )
import { ref, watch } from 'vue';
const msg = ref({ age: 21 });
watch(
    () => msg.value.age, // 需要使用 `.value` 指定 ref 定义的引用类型数据
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log('newVal', newVal);

对象的引用类型属性

  • 需要通过 [getter 函数] 指定监听的属性(如果直接写监听的属性,会抛出警告)
  • oldVal 等于 newVal,因为它们是同一个对象(引用类型数据操作的是内存地址)
  1. 监听 reactive 对象的引用类型的属性:watch( () => 变量名.属性名, (oldVal, newVal) => {}[, 配置对象] )
<template>
    <p>msg.job.j1.salary: {{ msg.job.j1.salary }}</p>
    <button @click="msg.job.j1.salary++">点击修改</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const msg = reactive({ job: { j1: { salary: 20 } } });
watch(
    () => msg.job,
    (oldVal, newVal) => {
        console.log('oldVal', oldVal); // oldVal Proxy {j1: {…}}
        console.log(oldVal === newVal); // true
    { deep: true } // 设置 deep: true 深度监听
</script>
  1. 监听 ref 对象的引用类型属性:watch( () => 变量名.value.属性名, (oldVal, newVal) => {}[, 配置对象] )
<template>
    <p>msg.job.j1.salary: {{ msg.job.j1.salary }}</p>
    <button @click="msg.job.j1.salary++">点击修改</button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
let msg = ref({ job: { j1: { salary: 20 } } });
watch(
    () => msg.value.job, // 需要使用 `.value` 指定 ref 定义的引用类型数据
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log(oldVal === newVal);
    { deep: true }
</script>

获取引用类型数据的 oldVal

  • 对于数组数据,我们可以使用 [回调函数] + [解构赋值],以获取 oldVal 和 newVal 值

    此时,会强制深度监听

<template>
    <p>faMsg: {{ faMsg }}</p>
    <button @click="faMsg[0] += 1">修改</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const faMsg = reactive([1, 2, 3]);
watch(
    () => [...faMsg], // 回调函数 + 解构赋值
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log('newVal', newVal);
    } /* 没有设置 deep: true */
</script>
  • 对于对象数据,我们可以使用 [ lodash 的 cloneDeep ] + [回调函数],以获取 oldVal 和 newVal 值

    此时,会强制深度监听

<template>
    <p>msg.job.j1.salary: {{ msg.job.j1.salary }}</p>
    <button @click="msg.job.j1.salary++">修改</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import _ from 'lodash'; // 引入 lodash
let msg = reactive({ job: { j1: { salary: 21 } } });
watch(
    () => _.cloneDeep(msg), // 使用 _.cloneDeep(数据)
    (oldVal, newVal) => {
        console.log('oldVal', oldVal);
        console.log('newVal', newVal);
    } /* 没有设置 deep: true */
</script>

监听多个数据

设置第一参数为 [多个数据源组成的数组],以监听多个数据:watch( [数据1, 数据2], (oldVal, newVal) => {}[, 配置对象] )

<template>
    <p>name: {{ name }}</p>
    <p>age: {{ age }}</p>
    <button @click="name += '~'">点击修改 name</button>
    <button @click="age++">点击修改 age</button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const name = ref('superman');
const age = ref(21);
watch([name, age], (oldVal, newVal) => {
    console.log('oldVal', oldVal); // 旧值组成的数组
    console.log('newVal', newVal); // 新值组成的数组
});
</script>

watchEffect

  • 接收一个回调函数 callBack
  • callBack 会被立即执行,同时追踪 callBack 里面用到的响应式数据,并在数据变更时重新执行该函数
<template>
    <p>obj.name: {{ obj.name }}</p>
    <p>num: {{ num }}</p>
    <button @click="obj.name += '~'">点击修改 obj.name</button>
    <button @click="num++">点击修改 num</button>
</template>
<script setup lang="ts">
import { reactive, ref, watchEffect } from 'vue';
let obj = reactive({ name: 'superman' });
let num = ref(0);
watchEffect(() => {
    // 回调函数会在创建侦听器时立即执行
    // 只要这里面的响应式数据被修改了, 就会再执行一次
    let wObj = obj.name;
    let wNum = num.value;
    console.log('watchEffect: ', wObj, wNum);
});
</script>

注意:watchEffect 仅会在其同步执行期间,才追踪依赖
在使用异步回调时,只有 [在第一个 await 正常工作前访问到的属性] 会被追踪

与 watch 的不同:

  1. watch 只追踪明确侦听的数据源,而不会追踪任何在回调中访问到的东西;watchEffect 会追踪在回调函数中用到的所有数据源
  2. watch 仅在数据源发生变化时触发回调;watchEffect 还会在监听创建时触发回调

回调的触发时机

更改响应式状态时,它可能会同时触发 [Vue 组件更新] & [侦听器回调]

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用
这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态

如果想在侦听器回调中能访问被 Vue 更新之后的DOM,需要配置 flush: 'post'

watch(source, callback, { flush: 'post' })
watchEffect(callback, { flush: 'post' })

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()

import { watchPostEffect } from 'vue'
watchPostEffect(() => { /* 在 Vue 更新后执行 */ })
                    # computed- 在组合式 API 中,计算属性 computed 是一个函数1. 完整写法 - 接收一个 [配置对象] 作为参数:`computed(   {   get(){ return X },   set(val){ }   }   )`   配置对象有 2 个配置项:getter、setter;     getter:获取当前计算属性时触发;返回一个 ref 对象;     setter:修改当前计算属性时触发;新值会作为参数传入;     computed 会自动追踪响应
				
一、computedwatch 都可以观察页面的数据变化。当处理页面的数据变化时,我们有时候很容易滥用watch。 而通常更好的办法是使用computed属性,而不是命令是的watch回调。  二、computed: 在vue的 模板内({{}})是可以写一些简单的js表达式的 ,很便利。但是如果在页面中使用大量或是复杂的表达式去处理数据,对页面的维护会有很大的影响。这个时候就需要用到computed 计算属性来处理复杂的逻辑运算 1.优点:  在数据未发生变化时,优先读取缓存。computed 计算属性只有在相关的数据发生变化时才会改变要计算的属性,当相关数据没有变化是,它会读取缓存。而不必想 motheds方法 和 watch 方法是的每次都去执行函数。 2.setter 和 getter方法:(注意在vue中书写时用set 和 get)  setter 方法在设置值是触发。  getter 方法在获取值时触发。 三、watch: 虽然计算属性在大多数情况下是非常适合的,但是在有些情况下我们需要自定义一个watcher,在数据变化时来执行异步操作,这时watch是非常有用的。
watchEffect 的返回值: watchEffect 的 onCleanup 回调参数:watchEffect 还有一个回调参数,此参数它也是一个函数,作用是清理副作用。 监听一个变量值的变化: 监听多个变量值的变化: 监听对象中具体的值: 监听ref的引用对象(添加参数3): 监听 reactive 定义的响应变量: const name = ref('漩涡鸣人'); const {username, password} = toRefs(reactive({ username: '宇智波带土', password: 'zs1262597806' watch const countCom = computed(() => count.value + 1); console.log('count', count); // RefImpl { ..., value: 1 } console.log('count', countCom)
看下(有this ,但是 vue3 是没有的,我们说过vue3的this是undefined) 与vue2 中的computed 配置功能一致。 (注意的是简写会在修改的时候警告,所以我们按照完整写法写入。)写法: 引入 computed vue2 中深度监听和立即监听 不过有两个坑要注意: 1:监听reactive定义的响应式数据时:oldvalue无法正确获取。强制开启了深度监听(deep配置失效)。 2:监听reactive定义响应式数据中某个属性时,deep配置有效。代码展示
Vuecomputedwatch 是两种不同的属性。 computed 是一种计算属性,它根据其他属性的变化动态计算属性值,并返回结果。computed 是基于它所依赖的响应式属性进行缓存的,只有在它所依赖的属性发生变化时才会重新计算。computed 比较适用于需要根据多个属性计算出一个结果的场景。 而 watch 则是一种观察属性变化的方式,它可以监听并响应单个属性的变化,从而执行相应的操作。watch 可以利用方法或回调函数来监听一个属性,当属性变化时会自动执行相应的代码。watch 比较适用于需要在属性变化时执行一些异步操作或复杂计算的场景。