Vue3 响应式API:高级函数(二)

shallowRef()

  • shallowRef 是一个特殊的 ref 创建函数,它允许你创建一个只追踪顶层属性变化的响应式引用。与 ref 不同的是,shallowRef 创建的响应式引用对其内部值的深层嵌套属性是不敏感的,也就是说,只有当 shallowRef 的 .value 被一个新对象替换时,Vue 的响应式系统才会追踪这个变化。
  • shallowRef 的主要作用是创建一个只对其顶层属性敏感的响应式引用。这在某些场景下非常有用,比如当你想要引用一个大型对象或数组,并且只需要关心该对象或数组的引用变化,而不关心其内部属性的变化时。但是,请注意不要意外地修改其内部属性,因为这可能不会触发视图更新。
import { shallowRef } from 'vue';  
  
// 创建一个 shallowRef  
const shallow = shallowRef({  
  nested: {  
    prop: 'Hello Vue 3!'  
  }  
});  
// 修改 shallowRef 的顶层属性(即引用变化),这将触发视图更新  
shallow.value = {  
  newNested: {  
    prop: 'New value!'  
  }  
};  
// 尝试修改 shallowRef 的嵌套属性(内部对象的变化),这不会触发视图更新  
shallow.value.nested.prop = 'This change will not trigger a view update.';

triggerRef()

  • 强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
  • 如果你确实在某个地方看到了 triggerRef() 这样的函数,那么它可能是某个库或应用程序中定义的自定义函数,而不是 Vue 3 官方 API 的一部分。
const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)

customRef()

  • customRef 是一个高阶函数,它允许你创建一个自定义的 ref,以精细地控制 ref 的 get 和 set 行为。当你需要自定义响应式引用的行为时,customRef 是非常有用的。
  • customRef 接收一个工厂函数作为参数,该工厂函数返回一个对象,该对象必须包含 get 和 set 方法。get 方法返回引用的值,而 set 方法接收新的值并更新引用。
import { customRef, ref } from 'vue';  
  
function customRefWithValidation(value, validator) {  
  // 使用内置的 ref 创建一个响应式引用  
  const innerRef = ref(value);  
  
  // 返回一个自定义的 ref 对象  
  return customRef((track, trigger) => {  
    // get 方法:返回内部 ref 的值  
    return {  
      get() {  
        track(); // 通知 Vue 这个 ref 正在被读取  
        return innerRef.value;  
      },  
      set(newValue) {  
        // 可以在这里添加自定义逻辑,比如验证  
        if (validator(newValue)) {  
          innerRef.value = newValue;  
          trigger(); // 通知 Vue 这个 ref 的值已经改变  
        } else {  
          console.warn('Invalid value!');  
        }  
      }  
    };  
  });  
}  
  
// 使用自定义的 ref  
const validatedRef = customRefWithValidation(0, (value) => typeof value === 'number' && value >= 0);  
  
// 设置值  
validatedRef.value = 5; // 成功  
validatedRef.value = -1; // 失败,并在控制台中打印警告

shallowReactive()

  • 创建一个浅层的响应式对象。与 reactive 不同,shallowReactive 只会对对象的顶层属性进行响应式追踪,而不会递归地追踪其嵌套对象的属性。
  • 不要意外修改嵌套属性:由于 shallowReactive 不会追踪嵌套对象的属性变化,因此如果你尝试修改嵌套对象的属性并期望视图更新,你可能会遇到意外的行为。
  • 与 reactive 的对比:与 reactive 不同,shallowReactive 是浅层的,只追踪顶层属性的变化。如果你需要追踪嵌套对象的属性变化,请使用 reactive。
  • 与其他工具函数的结合使用:你可以将 shallowReactive 与其他 Vue 3 的响应式工具函数(如 watch、computed 等)结合使用,以实现更复杂的响应式逻辑。
import { shallowReactive } from 'vue';  
  
const state = shallowReactive({  
  nested: {  
    prop: 'Hello Vue 3!'  
  },  
  shallowProp: 'Shallow prop'  
});  
  
// 修改顶层属性,将触发视图更新  
state.shallowProp = 'New shallow prop value';  
  
// 修改嵌套对象的属性,不会触发视图更新  
state.nested.prop = 'New nested prop value'; // 这个变化不会被追踪

shallowReadonly()

  • 创建一个浅层的只读响应式对象。与 readonly 函数类似,shallowReadonly 会创建一个响应式对象的代理,但不同之处在于 shallowReadonly 只会将对象的顶层属性标记为只读,而不会递归地将对象的嵌套属性也标记为只读。
  • 不要意外修改嵌套属性:虽然 shallowReadonly 不会阻止你修改嵌套对象的属性,但这样做并不会触发 Vue 的响应式系统,因此不会触发相关的依赖更新。
  • 与 readonly 的对比:与 readonly 不同,shallowReadonly 是浅层的,只会将对象的顶层属性标记为只读。如果你需要递归地将对象的所有属性(包括嵌套属性)都标记为只读,请使用 readonly。
  • 与其他工具函数的结合使用:你可以将 shallowReadonly 与其他 Vue 3 的响应式工具函数(如 computed、watch 等)结合使用,以实现更复杂的响应式逻辑。但请记住,由于 shallowReadonly 是浅层的,它不会追踪嵌套属性的变化。
import { shallowReadonly } from 'vue';  
  
const original = {  
  nested: {  
    prop: 'Hello Vue 3!'  
  },  
  shallowProp: 'Shallow prop'  
};  
  
const state = shallowReadonly(original);  
  
// 读取顶层属性  
console.log(state.shallowProp); // 'Shallow prop'  
  
// 读取嵌套对象的属性  
console.log(state.nested.prop); // 'Hello Vue 3!'  
  
// 尝试修改顶层属性,会抛出错误  
// state.shallowProp = 'New shallow prop value'; // TypeError: Cannot assign to read only property 'shallowProp' of object  
  
// 尝试修改嵌套对象的属性,这将成功(但Vue不会追踪这个变化)  
state.nested.prop = 'New nested prop value'; // 不会抛出错误,但Vue不会追踪这个变化

toRaw()

  • 用于获取一个响应式对象的原始对象。这意味着你可以通过 toRaw 函数绕过 Vue 的响应式系统,直接访问原始的非代理对象。这在某些需要直接操作原始对象的场景下可能会很有用。
  • 不要意外修改原始对象:虽然 toRaw 允许你直接访问和修改原始对象,但这可能会导致与 Vue 的响应式系统不一致的情况。请确保你了解这样做的后果,并在必要时采取适当的措施来同步状态。
  • 与 Vue 的响应式系统分离:通过 toRaw 获取的原始对象与 Vue 的响应式系统完全分离。这意味着对原始对象的任何修改都不会触发 Vue 的依赖更新或视图渲染。
  • 谨慎使用:由于 toRaw 可以绕过 Vue 的响应式系统,因此在使用时需要格外小心。请确保你了解你的代码和 Vue 的响应式系统的工作原理,并谨慎地决定何时使用 toRaw。
import { reactive, toRaw } from 'vue';  
  
const original = {  
  count: 0  
};  
  
const state = reactive(original);  
  
// 通过响应式对象修改值  
state.count = 1;  
  
// 获取原始对象  
const raw = toRaw(state);  
  
// 原始对象和响应式对象不是同一个对象  
console.log(state === raw); // false  
  
// 原始对象和响应式对象的内容是相同的  
console.log(state.count === raw.count); // true  
  
// 直接修改原始对象,响应式系统不会追踪这个变化  
raw.count = 2;  
  
// 响应式对象的内容没有被改变  
console.log(state.count); // 1

markRaw()

  • 用于将一个对象标记为非响应式的,从而确保它不会被 Vue 的响应式系统转换为代理对象。这在某些情况下可能是有用的,特别是当你有一个不应该被 Vue 追踪其属性变化的对象时。
  • 不要意外修改非响应式对象:虽然 markRaw 允许你创建一个非响应式的对象,但这并不意味着你可以随意修改它而不用担心任何副作用。如果你正在与组件或其他 Vue 的功能交互,并且错误地修改了一个非响应式对象,可能会导致不一致的行为或难以调试的问题。
  • 谨慎使用:由于 markRaw 会绕过 Vue 的响应式系统,因此在使用时需要格外小心。请确保你了解你的代码和 Vue 的响应式系统的工作原理,并谨慎地决定何时使用 markRaw。
  • 不与其他响应式功能结合使用:一旦一个对象被 markRaw 标记为非响应式,你就不应该再使用 Vue 的其他响应式功能(如 reactive、ref、computed 等)来尝试使其变得响应式。这样做可能会导致不可预测的行为。
import { reactive, markRaw } from 'vue';  
  
// 创建一个普通的对象  
const rawObject = {  
  value: 'I am raw!'  
};  
  
// 使用 markRaw 标记该对象为非响应式  
const nonReactiveObject = markRaw(rawObject);  
  
// 尝试使用 reactive 包裹它,但 Vue 会识别到它已经被标记为非响应式  
const reactiveWrapper = reactive(nonReactiveObject);  
  
// reactiveWrapper 实际上与 nonReactiveObject 是同一个对象,没有被转换为代理  
console.log(reactiveWrapper === nonReactiveObject); // true  
  
// 修改 rawObject 的属性,Vue 不会追踪这个变化  
rawObject.value = 'I am still raw!';  
  
// 因为 reactiveWrapper 是 rawObject 的引用,所以它的值也会变化,但 Vue 并没有追踪这个变化  
console.log(reactiveWrapper.value); // 'I am still raw!'

effectScope()

  • effectScope 是一个相对高级的工具函数,它允许你创建一个独立的作用域(scope)来管理副作用(effects)。副作用通常指的是那些依赖于响应式数据变化并执行相应操作的函数,例如计算属性、侦听器(watchers)或渲染函数。
  • 谨慎使用:effectScope 是一个相对高级的工具,通常用于处理复杂的响应式逻辑。在大多数情况下,使用 Vue 提供的 reactive、ref、computed 和 watch 等 API 就足够了。
  • 资源管理:当使用 onScopeDispose 添加清理逻辑时,请确保正确管理资源,以避免内存泄漏或其他问题。
  • 副作用的触发:在 effectScope 内的副作用仍然遵循 Vue 的响应式系统规则。只有当它们依赖的响应式数据发生变化时,它们才会被触发执行。
  • 性能考虑:创建过多的作用域或副作用可能会对性能产生影响。因此,请确保在使用 effectScope 时考虑性能因素,并尽量保持作用域和副作用的数量在可控范围内。
import { reactive, effectScope, effect, onScopeDispose } from 'vue';  
  
// 创建一个响应式对象  
const state = reactive({ count: 0 });  
  
// 创建一个新的 effectScope  
const scope = effectScope();  
  
// 在该作用域内创建一个 effect  
scope.run(() => {  
  effect(() => {  
    console.log('count changed:', state.count);  
  });  
  
  // 当作用域被销毁时,执行一些清理逻辑  
  onScopeDispose(() => {  
    console.log('Scope disposed!');  
  });  
});  
  
// 触发 state.count 的变化  
state.count++; // 输出 "count changed: 1"  
  
// 你可以稍后手动停止并销毁作用域及其内部的 effects  
scope.stop(); // 销毁作用域,输出 "Scope disposed!"  
  
// 再次触发 state.count 的变化,但不会有任何输出  
// 因为作用域已经被销毁了  
state.count++;

getCurrentScope()

  • getCurrentScope 是一个与 effectScope 相关的工具函数,但它并不是 Vue 官方 API 的一部分。
  • 在大多数情况下,你不需要直接访问当前的作用域。Vue 的响应式系统会自动管理作用域的创建、销毁和其中的副作用。但是,如果你确实需要这样的功能(可能是为了调试或高级用例),你可以考虑使用 Vue 的内部 API 或自己实现类似的逻辑。
  • 如果你确实需要类似 getCurrentScope 的功能,你可能需要考虑以下替代方案:

使用自定义标记:在你的代码中,你可以为每个 effectScope 创建一个唯一的标识符,并在创建作用域时将其存储在某个全局或局部的可访问位置。然后,你可以通过该标识符来获取相应的作用域。
使用外部库或插件:如果你发现自己经常需要这样的功能,你可以考虑使用第三方库或插件来扩展 Vue 的功能。这些库或插件可能已经实现了类似 getCurrentScope 的功能,并且经过了广泛的测试和验证。
重新设计你的代码:在大多数情况下,你可能不需要直接访问当前的作用域。相反,你可以重新设计你的代码,使其更加模块化和可维护。例如,你可以将相关的副作用组织在同一个组件或模块中,并使用Vue 的响应式系统来自动管理它们。

onScopeDispose()

  • onScopeDispose 并不是一个直接暴露给用户的 API,但它是一个在内部使用的重要概念,特别是在与 effectScope 一起使用时。onScopeDispose 主要用于注册当特定作用域(scope)被停止或销毁时应当执行的回调函数。
  • 当你在 effectScope 的上下文中创建副作用(如使用 effect 或 watchEffect)时,你可以使用 onScopeDispose 来注册一个清理函数,这个函数会在该作用域被停止或销毁时执行。
import { reactive, effectScope, effect } from 'vue';  
  
// 创建一个响应式对象  
const state = reactive({ count: 0 });  
  
// 创建一个新的 effectScope  
const scope = effectScope();  
  
// 在该作用域内创建一个 effect 并注册一个清理函数  
scope.run(() => {  
  // 创建一个 effect  
  const cleanupEffect = effect(() => {  
    console.log('count changed:', state.count);  
  });  
  
  // 注册一个当作用域被销毁时执行的清理函数  
  // 注意:这里不是直接使用 onScopeDispose,而是模拟其功能  
  const onDispose = () => {  
    console.log('Scope disposed! Cleaning up...');  
    // 在这里执行清理逻辑,比如取消订阅、移除事件监听器等  
    // cleanupEffect.stop(); // 如果需要的话,可以停止这个特定的 effect  
  };  
  
  // 假设我们有一个机制来在作用域停止时调用 onDispose  
  // ...(在 Vue 内部,这是自动处理的)  
  
  // 模拟作用域停止的事件(在 Vue 中,你不需要手动这样做)  
  // scope.stop(); // 这会触发上面定义的 onDispose 函数  
});  
  
// 触发 state.count 的变化  
state.count++; // 输出 "count changed: 1"  
  
// 假设在某个时候,作用域被停止了(在 Vue 中可能是组件卸载时)  
// scope.stop(); // 如果这行代码被取消注释,会输出 "Scope disposed! Cleaning up..."
  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

**之火

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值