• vue3响应式


    vue3响应式

    vue3实现响应式的方法有两种:

    第一种运用组合式API中的reactive直接构建响应式,组合式API的出现让我们可以直接用setup函数来处理之前的大部分逻辑,同时也避免了this的使用,像datawatchcomputed,生命周期函数都声明在setup函数中,这样就像react-hooks一样提升代码的复用率,逻辑性更强。

    第二种就是使用传统的 data(){ return{} } 的形式,vue3并没有放弃对vue2写法的支持,而是对vue2的写法完全兼容。

    响应式基础API

    首先,让我们来看看vue3为响应式提供的一些基础API.

    reactive

    reactive用于将数据变成响应式数据。调用reactive后返回的对象是响应式副本而非原始对象。其原理就是将传入的数据包装成一个Proxy对象。

    import { reactive, watchEffect } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    data === obj // false
    
    watchEffect(() => {
      // 用于响应性追踪
      console.log(obj.count);
    });
    
    setTimeout(() => {
      obj.count++;
    }, 2000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    响应式转换是“深层”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。

    reactive用于复杂数据类型,比如对象和数组等,当传入基础数据类型时,默认情况下修改数据,界面不会自动更新,如果想更新,可以通过重新赋值的方式。

    readonly

    接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的,不能改变值,否则是报错。

    import { reactive, watchEffect, readonly } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    const copy = readonly(obj);
    
    watchEffect(() => {
      // 用于响应性追踪
      console.log(obj.count)
    });
    
    setTimeout(() => {
      obj.count++;
      copy.count++;
    }, 2000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    isProxy

    检查对象是否是由 reactivereadonly 创建的 proxy

    import { reactive, readonly, isProxy } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    const copy = readonly(obj);
    
    console.log(isProxy(obj));     // true
    console.log(isProxy(copy));    // true
    console.log(isProxy(data));    // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    isReactive

    检查对象是否是由 reactive 创建的响应式代理。如果该代理是 readonly 创建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true

    import { reactive, readonly, isReactive } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    const copy = readonly(obj);
    const immute = readonly(data);
    
    console.log(isReactive(obj));     // true
    console.log(isReactive(copy));    // true
    console.log(isReactive(data));    // false
    console.log(isReactive(immute))   // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    isReadonly

    检查对象是否是由 readonly 创建的只读代理。

    import { reactive, readonly, isReadonly } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    const copy = readonly(obj);
    const immute = readonly(data);
    
    console.log(isReadonly(obj));     // false
    console.log(isReadonly(copy));    // true
    console.log(isReadonly(data));    // false
    console.log(isReadonly(immute))   // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    toRaw

    返回 reactivereadonly 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。

    import { reactive, readonly, toRaw } from 'vue';
    const data = {
      count: 0
    };
    const obj = reactive(data);
    const immute = readonly(data);
    
    console.log(toRaw(obj) === data);      // true
    console.log(toRaw(immute) === data);   // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    markRaw

    标记一个对象,使其永远不会转换为 proxy。返回对象本身。

    import { reactive, readonly, isReactive, markRaw } from 'vue';
    const data = markRaw({
      count: 0
    });
    
    const obj = reactive(data);
    const immute = readonly(data);
    
    console.log(isReactive(obj));  // false
    console.log(obj === data);     // true
    console.log(immute === data);  // true
    
    // 即使嵌套在其他响应式队中也是非响应式的
    const wrap = reactive({data});
    console.log(isReactive(wrap.data)); // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    但是如果对标记对象内的属性进行响应化,是可以的。

    const count = reactive({
      // data被标记,data.count未被标记
      count: data.count
    })
    
    console.log(isReactive(count));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    shallowReactive

    创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换。

    import { shallowReactive, isReactive, watchEffect } from 'vue';
    const data = {
      count: 0,
      nested: {
        count: 0
      }
    };
    
    const obj = shallowReactive(data);
    
    watchEffect(() => {
      // 追踪obj.count的变化
      console.log(obj.count);
    });
    watchEffect(() => {
      // 追踪obj.nested.count的变化
      console.log(obj.nested.count);
    });
    
    obj.count++;
    obj.nested.count++;
    console.log(data.count);              // 0
    console.log(data.nested.count);       // 1
    console.log(isReactive(obj.nested));  // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    shallowReadonly

    创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换。

    import { shallowReadonly, isReadonly } from 'vue';
    const data = {
      count: 0,
      nested: {
        count: 0
      }
    };
    
    const obj = shallowReadonly(data);
    
    // 报错
    obj.count++;
    
    obj.nested.count++;
    console.log(isReadonly(obj.nested));  // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ref

    接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value值,指向该内部值。跟reactive类似,也是将数据变成响应式。

    import { ref, watchEffect } from 'vue';
    
    const count = ref(0);
    const obj = ref({
      count: 0
    })
    console.log(obj)
    watchEffect(() => {
      console.log(count.value);
    })
    watchEffect(() => {
      console.log('obj.value.count: ', obj.value.count);
    })
    
    count.value++;
    obj.value.count++;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ref和reactive的区别

    • ref是把值类型添加一层包装,使其变成响应式的引用类型的值。ref(0) --> reactive( { value:0 })
    • reactive 则是引用类型的值变成响应式的值。

    两者的区别只是在于是否需要添加一层引用包装,对于对象而言,添加一层包装后会被reactive处理为深层的响应式对象,在调用unref后就能看到其实对象是一个Reactive对象

    像上面的例子,使用ref同样可以将对象响应化,不过访问的时候需要调用value.去访问内部属性。所以对于对象而言,最好使用reative去响应化处理。

    unref

    如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。

    import { ref, unref } from 'vue';
    
    const count = ref(0);
    const obj = ref({
      count: 0
    })
    console.log(unref(count));  // 0
    console.log(unref(obj))     // Reactive对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    toRef

    可以用来为源响应式对象上的某个属性新创建一个 ref。然后,ref 可以被传递,它会保持对其源属性的响应式连接。

    import { reactive, toRef } from 'vue';
    
    const obj = reactive({
      count: 0
    })
    
    const countRef = toRef(obj, 'count');
    
    countRef.value++;
    console.log(obj.count)    // 1
    
    obj.count++;
    console.log(countRef)     // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    toRefs

    将响应式对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的 ref

    import { reactive, toRefs } from 'vue';
    
    const obj = reactive({
      count: 0
    })
    
    const countRef = toRefs(obj);
    
    countRef.value++;
    console.log(obj.count)    // 1
    
    obj.count++;
    console.log(countRef.count.value)     // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    toRefs 只会为源对象中包含的属性生成 ref。如果要为特定的属性创建 ref,则应当使用 toRef

    isRef

    检查值是否为一个 ref 对象。

    import { isRef, ref } from 'vue';
    const count = ref(0);
    console.log(isRef(count));    // true
    
    • 1
    • 2
    • 3

    customRef

    创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并且应该返回一个带有 getset 的对象。

    使用自定义 ref 通过 v-model 实现 debounce 的示例:

    <input v-model="text" />
    
    • 1
    function useDebouncedRef(value, delay = 200) {
      let timeout
      return customRef((track, trigger) => {
        return {
          get() {
            track()
            return value
          },
          set(newValue) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
              value = newValue
              trigger()
            }, delay)
          }
        }
      })
    }
    
    export default {
      setup() {
        return {
          text: useDebouncedRef('hello')
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    shallowRef

    创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

    const obj = shallowRef({})
    // 改变 ref 的值是响应式的
    obj.value = {}
    // 但是这个值不会被转换。
    isReactive(obj.value) // false
    
    • 1
    • 2
    • 3
    • 4
    • 5

    triggerRef

    手动执行与 shallowRef 关联的任何作用。

    const shallow = shallowRef({
      greet: 'Hello, world'
    })
    
    // 第一次运行时记录一次 "Hello, world"
    watchEffect(() => {
      console.log(shallow.value.greet)
    })
    
    // 这不会触发作用 (effect),因为 ref 是浅层的
    shallow.value.greet = 'Hello, universe'
    
    // 触发副作用 "Hello, universe"
    triggerRef(shallow)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    computed

    接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
    
    console.log(plusOne.value) // 2
    
    plusOne.value++ // 错误
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或者,接受一个具有 getset 函数的对象,用来创建可写的 ref 对象。

    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: val => {
        count.value = val - 1
      }
    })
    
    plusOne.value = 1
    console.log(count.value) // 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    watch

    vue3中组合式api watchvue2中选项式的watch完全等效。watch 需要监听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在监听源发生变化时被调用。

    watchEffect 相比,watch 允许我们:

    • 惰性地执行副作用;
    • 更具体地说明应触发监听器重新运行的状态;
    • 访问被监听状态的先前值和当前值。
    监听单一源

    源数据可以是一个reactive,也可以直接是一个 ref
    语法

    watch( name , callback, options )
    • 1
    • name: 需要监听的属性或者返回值的getter函数
    • callback: 属性改变后执行的方法,接受两个参数
      • newVal: 新值
      • oldVal: 旧值
    • options: 配置项,可配置如下
      • deep: Boolean, 是否深度监听
      • immediate: Boolean,是否立即执行
    // 侦听reactive
    const state = reactive({ count: 0, value: 1 })
    // 只监听对象中的count
    watch(
      // 使用getter函数保证只监听了state中的count
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )
    
    // 侦听state中所有属性
    watch(state, (newVal, oldVal) => {
      // ...
    })
    
    // 直接侦听一个 ref
    const count = ref(0)
    watch(count, (count, prevCount) => {
      /* ... */
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    监听多个源

    使用数组来同时侦听多个源数据,当任一源数据发生改变都会触发watch

    const state = reactive({ count: 0, value: 1 });
    const count = ref(0)
    watch([state, count], ([newState, newCount], [oldState, oldCount]) => {
      console.log(newState);
      console.log(newCount);
    })
    
    state.count++;
    count.value++;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    深度监听

    watch的第三个参数中传入deep: true

    const state = reactive({ count: 0, value: { status: false } });
    watch(() => state, (newVal, oldVal) => {
      console.log('不会触发')
    })
    
    watch(() => state, (newVal, oldVal) => {
      console.log('触发深度监听')
    }, {
      deep: true
    })
    
    state.value.status = true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    由于getter方法只返回state,对没有对内部的对象进行监听,因此内部对象的属性发生改变不会触发watch

    当没有使用getter方法而是传入state这个reactive数据,则不需要设置deep: true都会进行深度监听。

    立即执行

    watch的第三个参数中传入immediate: true,当传入数据就会执行一次。

    watchEffect

    立即执行传入的一个函数,响应式追踪其依赖,在其依赖变更时重新运行该函数

    它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听

    const count = ref(0)
    
    watchEffect(() => console.log(count.value))
    // -> logs 0
    
    setTimeout(() => {
      count.value++
      // -> logs 1
    }, 100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    停止监听

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

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

    const stop = watchEffect(() => {
      /* ... */
    })
    
    // later
    stop()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    清除副作用

    有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
    watchEffect(onInvalidate => {
      const token = performAsyncOperation(id.value)
      onInvalidate(() => {
        // id has changed or watcher is stopped.
        // invalidate previously pending async operation
        token.cancel()
      })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    企企通亮相广东智能装备产业发展大会:以数字化采购促进智能装备产业集群高质量发展
    Spring Boot 实现文件本地以及OSS上传
    【深度学习实验】注意力机制(一):注意力权重矩阵可视化(矩阵热图heatmap)
    UG\NX二次开发 获取工作部件的事例 UF_ASSEM_ask_work_occurrence
    [附源码]java毕业设计-室内田径馆预约管理系统
    RPA是什么?推荐让电商运营10倍提效的自动化工具
    国际刑事法院系统遭网络间谍攻击
    初识RabbitMQ
    k8s-dynamic-pvc
    Java项目:springboot+vue电影院会员管理系统
  • 原文地址:https://blog.csdn.net/qq_42880714/article/details/127819812