• Vue 3响应式系统全解析:深入ref、reactive、computed、watch及watchEffect


    一、ref与breactive

    宏观角度看:

    • 1.ref 用来定义:基本类型数据、对象类型数据
    • 2.reactive 用来定义:对象类型数据

    区别:

    • 【ref 】创建的变量必须使用.value
    • 【reactive】 重新分配一个新对象,会失去响应式(可以使用0biect.assign 去整体替换)。

    使用原则:

    • 1.若需要一个基本类型的响应式数据,必须使用ref
    • 2.若需要一个响应式对象,层级不深,ref、reactive 都可以。
    • 3.若需要一个响应式对象,且层级较深,推荐使用reactive

    二、toRefs与toRef

    1.toRef

    • 作用:从 reactive 对象中提取单个属性,创建一个 ref。
    • 使用场景:当你需要对 reactive 对象的某个具体属性进行单独的响应式处理时。

    基本用法

    import { reactive, toRef } from 'vue';
    const person = reactive({ name: 'John Doe' });
    const nameRef = toRef(person, 'name');
    console.log(nameRef.value); // John Doe
    
    • 1
    • 2
    • 3
    • 4

    2.toRefs

    • 作用:将 reactive 对象转换为多个 ref 对象的集合。
    • 使用场景:适用于需要将整个 reactive 对象的多个属性分别作为 ref 处理的情况。

    基本用法

    import { reactive, toRefs } from 'vue';
    
    const person = reactive({ name: 'John Doe', age: 30 });
    const { name, age } = toRefs(person);
    console.log(name.value); // John Doe
    console.log(age.value);  // 30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结论

    • toRef 创建单个 ref,用于单个属性的响应式绑定。
    • toRefs 创建多个 ref,用于将整个对象的属性分别响应式绑定。
    • 选择使用 toRef 或 toRefs 取决于你需要对 reactive 对象执行的响应式操作的复杂性。

    三、computed计算属性

    计算属性是 Vue 3 中一种特殊的响应式变量,它根据其他响应式数据自动计算值。

    基本用法

    import { computed, ref } from 'vue';
    const count = ref(0);
    const doubledCount = computed(() => count.value * 2);
    
    • 1
    • 2
    • 3

    特点

    • 自动更新:当依赖的数据变化时,计算属性会自动重新计算。
    • 惰性计算:只有当依赖项变化时,计算逻辑才会执行。
    • 结果缓存:多次访问计算属性会得到相同的结果,而不会重复计算。

    四、watch

    作用:监视数据的变化(和 Vue2 中的 watch 作用一致)
    特点:Vue3 中的 watch 只能监视以下四种数据:

    1.ref 定义的数据。
    2.reactive 定义的数据。
    3.函数返回一个值(getter 函数)。
    4.一个包含上述内容的数组。

    watch 监听

    ⭐情况一

    监视【ref】 直接写数据名即可,监视的是其 value 值的改变。

    示例:

    import { watch, ref } from 'vue';
    
    const count = ref(0);
    
    // 监视 ref 'count' 的变化
    watch(count, (newValue, oldValue) => {
      console.log(`变化前:${oldValue} 变化后: ${newValue}`);
    },{
        deep: true,       	// 深度监听 state 对象内部的变化
        immediate: true,  	// 当设置为 true 时,watch 会在开始监视后立即执行一次回调函数。
        flush: 'pre',     	// 控制副作用(如 DOM 更新)的刷新时机。可以是 'pre' 或 'post',或者一个包含这些值的数组。
        onTrack: () => {},  // 依赖项被追踪时调用
        onTrigger:() =>{},  // 依赖项变化时调用
        computed: true    	// 计算属性模式
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ⭐情况二

    监视【ref】深层要手动开启深度监视

    注意:
    若修改的是ref定义的对象中的属性,newValue 和 oldValue 都是新值,因为它们是同一个对象。
    若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

    示例:

    import { watch, ref } from 'vue';
    
    const person = ref({ name: 'John', age: 30 });
    
    // 监视 ref 'person' 的深层属性变化
    watch(
      () => person.value, // 使用函数返回 ref 的 value,以确保每次都是最新的值
      (newValue, oldValue) => {
        // 回调函数会在 person 对象的属性变化时触发
        console.log(`变化前:${oldValue.name} 变化后:${newValue.name}`);
      },
      { deep: true } // 开启深度监视
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这个例子中:

    • person 是一个通过 ref 创建的响应式引用,它包含一个对象,该对象有 name 和 age 属性。
    • 使用 watch 函数并传入一个函数,该函数返回 person.value。这样做是因为我们需要监视 person 对象内部属性的变化,而不是 person 这个引用本身。
    • 参数 { deep: true } 开启了深度监视,这样 watch 就能够追踪到对象内部属性的变化。
    • 当 person 对象的 name 或 age 属性发生变化时,提供的回调函数会被触发。

    注意事项

    • 当你监视 ref 定义的对象并开启深度监视时,newValue 和 oldValue 将代表对象内部发生变化的属性的新旧值。
    • 如果整个 ref 定义的对象被一个新的对象替换,那么 newValue 将是新对象,而 oldValue 将是旧对象。
    • 开启深度监视可能会带来性能上的考虑,因为它会增加 Vue 需要追踪的变化的数量。

    ⭐情况三

    监视【reactive】定义的【对象类型】数据,默认开启深度监视(无法关闭)

    示例:

    import { reactive, watch } from 'vue';
    
    // 使用 reactive 创建一个响应式对象
    const person = reactive({
      name: 'John',
      details: {
        age: 30
      }
    });
    
    // 监视 reactive 对象的 'details.age' 属性
    watch(
      () => person.details.age,
      (newValue, oldValue) => {
        console.log(`Age changed from ${oldValue} to ${newValue}`);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这个例子中:

    • person 是一个使用 reactive 创建的响应式对象,它包含一个名为 details 的属性,该属性本身也是一个对象,包含 age 属性。
    • 使用 watch 来监视 person.details.age 属性的变化。由于 reactive 默认进行深度监视,因此不需要额外的 deep 选项即可追踪到 details 对象内部的 age 属性变化。

    注意事项

    • reactive 创建的响应式对象总是深度监视的,这意味着你无法关闭这个行为。
    • 当你使用 watch 来监视 reactive对象的属性时,即使属性是嵌套的,也不需要 deep 选项,因为深度监视是自动应用的。
    • 如果 reactive 对象的某个属性也是一个对象,并且你想要监视这个嵌套对象的属性,你可以直接使用 watch,并提供一个返回该嵌套属性的函数。

    通过这种方式,Vue 3 的 reactive API 提供了一种直观且强大的方式来创建和管理响应式状态,同时 watch API 允许你监视这些状态的变化,无需担心监视的深度问题。

    ⭐情况四

    监视响应式对象中的某个属性,且该属性是基本类型,要写成函数式
    监视响应式对象中的某个属性,且该属性是对象类型,可以直接写,也能写函数,更推荐写函数


    ⭐结论:监视的要是对象里的属性,那么最好写函数式
    ⭐注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

    基本类型属性的监视

    当监视响应式对象中的基本类型属性时,推荐使用函数式的方式来访问该属性的值。这是因为基本类型的属性不会触发对象的重新分配,所以每次获取的都是最新的值。

    示例:

    import { watch, reactive } from 'vue';
    
    const person = reactive({ name: 'John' });
    
    // 使用函数式监视响应式对象中的 'name' 属性
    watch(
      () => person.name,
      (newValue, oldValue) => {
        console.log(`变化前:${oldValue} 变化后:${newValue}`);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个例子中,我们通过函数 () => person.name 来获取 person 对象的 name 属性的值。每次 watch 触发时,都会调用这个函数以确保获取到最新的 name 值。

    对象类型属性的监视

    当监视响应式对象中的对象类型属性时,可以直接写属性名,也可以使用函数式。推荐使用函数式,尤其是当你需要监视对象内部属性的变化时。

    示例:

    const person = reactive({ detail: { address: '123 Main St' } });
    
    // 使用函数式监视 'detail' 对象内部的 'address' 属性
    watch(
      () => person.detail.address,
      (newValue, oldValue) => {
        console.log(`变化前: ${oldValue} 变化后:${newValue}`);
      },deep: true } // 开启深度监视,以追踪对象内部属性的变化
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个例子中,我们监视的是 person.detail.address。由于 address 是 detail 对象的属性,我们需要开启深度监视 { deep: true } 来确保当 address 发生变化时,watch 能够触发。
    我们通过函数 () => person.name 来获取 person 对象的 name 属性的值。每次 watch 触发时,都会调用这个函数以确保获取到最新的 name 值。

    ⭐情况五:监视上述的多个数据

    示例

    import { watch, reactive, ref } from 'vue';
    
    // 使用 reactive 创建一个响应式对象
    const person = reactive({
      name: 'John',
      car: 'Toyota'
    });
    
    // 使用 ref 创建一个响应式引用
    const age = ref(30);
    
    // 监视 person 对象的 'name' 属性和 age 引用
    watch([() => person.name, age], (newValues, oldValues) => {
      // newValues 和 oldValues 都是数组,分别包含新旧值
      console.log(`Name changed to ${newValues[0]}, Age changed to ${newValues[1]}`);
      // 如果只想监视 person.car 的变化,可以这样写:
      // watch([() => person.car, age], callback, options)
    }, { deep: true });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个例子中:

    • 使用 reactive 创建了一个响应式对象 person,它包含 name 和 car 属性。
    • 使用 ref 创建了一个响应式引用age。 使用 watch 来监视 person.name 和 age。
    • 这里通过传递一个数组给 watch的第一个参数来实现同时监视多个数据源。数组中的每个元素都是一个返回要监视数据的函数或响应式引用。
    • newValues 和oldValues 参数都是数组,它们包含了所有监视数据源的新旧值。数组中每个元素的顺序与 watch 第一个参数中的顺序相对应。
    • { deep: true } 表示对 person 对象进行深度监视,这样可以确保当对象内部的属性变化时,也能触发 watch
      的回调函数。

    注意事项

    • 当监视多个响应式数据源时,确保 newValues 和 oldValues 的索引与 watch 第一个参数中数据源的顺序相匹配。
    • 使用deep: true 选项可以确保当监视的对象或数组内部发生变化时,也能触发回调函数。

    五、watchEffect

    watchEffect 是 Vue 3 的一个工具,它用来观察你的数据。当你的数据变化时,它会做一些事情。

    使用方法

    1. 选择数据:首先,你需要有一些数据,比如一个数字。
    2. 告诉 watchEffect 观察这个数据。
    3. 决定数据变化时要做的事情:当这个数据变化时,watchEffect 会帮你执行一个操作,比如打印信息。

    示例

    // 假设我们有一个数字,我们想观察它的变化
    const count = 0;
    
    // 使用 `watchEffect` 观察 `count` 的变化
    watchEffect(() => {
      // 当 `count` 变化时,打印 `count` 的新值
      console.log(`New value of count is: ${count}`);
    });
    
    // 如果你改变了 `count` 的值,上面的打印就会执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    停止观察

    如果你后面不想观察了,可以告诉 watchEffect 停止。

    const stop = watchEffect(() => {
      // 逻辑
    });
    
    // 想要停止观察时,调用这个函数
    stop();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    Java项目依赖释放模式
    【深入了解操作系统进程:它们的角色、管理和重要性】
    【QML】警告Name is declared more than once
    银行排号叫号管理系统(MyEclipse+Java+GUI+MySQL)
    深入理解MongoDB的CRUD操作
    Hafnium之电源管理
    Considering process from Apple Pay further steps
    白盒测试之测试用例设计方法
    Delaunay三角网之逐点插入法(优化版本二)
    将web前端项目部署到github,在hbuilderx中部署github中的项目、对Github加速
  • 原文地址:https://blog.csdn.net/ZSZ1805355272/article/details/137916574