所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

Vue 3 customRef 完全指南:自定义响应式引用的终极教程

📖 概述

customRef() 是 Vue 3 中用于创建自定义响应式引用的组合式 API。它允许开发者完全控制响应式数据的读取和写入行为,为复杂的响应式逻辑提供了强大的灵活性。

🎯 基本概念

什么是 customRef?

customRef() 是一个工厂函数,它接受一个工厂函数作为参数,返回一个自定义的响应式引用。通过自定义 getset 函数,可以实现复杂的响应式逻辑。

核心特性

特性描述
完全控制自定义 getter 和 setter 逻辑
延迟计算支持懒加载和缓存机制
副作用处理精确控制依赖收集和触发更新
类型安全完整的 TypeScript 支持

🔧 函数签名

function customRef<T>(
  factory: (
    track: () => void,
    trigger: () => void
  ) => {
    get: () => T;
    set: (value: T) => void;
  }
): Ref<T>;

📋 参数说明

参数类型描述
track() => void依赖收集函数,在 getter 中调用
trigger() => void触发更新函数,在 setter 中调用

🎯 使用场景

1️⃣ 防抖输入框

创建带有防抖功能的输入框,避免频繁触发更新。

2️⃣ 异步数据加载

实现懒加载和缓存机制的响应式数据。

3️⃣ 数据验证和转换

在数据写入时进行验证和格式转换。

💻 代码示例

🚀 基础用法

import { customRef } from "vue";

// 创建一个简单的自定义 ref
const count = customRef((track, trigger) => {
  let value = 0;

  return {
    get() {
      track(); // 收集依赖
      return value;
    },
    set(newValue) {
      value = newValue;
      trigger(); // 触发更新
    },
  };
});

// 使用
console.log(count.value); // 0
count.value = 10;
console.log(count.value); // 10

⏱️ 防抖输入框

import { customRef } from "vue";

function useDebouncedRef(initialValue, delay = 300) {
  return customRef((track, trigger) => {
    let value = initialValue;
    let timeoutId = null;

    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      },
    };
  });
}

// 在组件中使用
const searchQuery = useDebouncedRef("", 500);

🔄 异步数据加载

import { customRef } from "vue";

function useAsyncRef(fetcher) {
  return customRef((track, trigger) => {
    let value = null;
    let loading = false;
    let error = null;

    const load = async () => {
      if (loading) return;

      loading = true;
      error = null;
      trigger();

      try {
        value = await fetcher();
      } catch (err) {
        error = err;
      } finally {
        loading = false;
        trigger();
      }
    };

    return {
      get() {
        track();
        if (value === null && !loading) {
          load();
        }
        return { value, loading, error };
      },
      set(newValue) {
        value = newValue;
        trigger();
      },
    };
  });
}

// 使用示例
const userData = useAsyncRef(() =>
  fetch("/api/user").then((res) => res.json())
);

✅ 数据验证

import { customRef } from "vue";

function useValidatedRef(initialValue, validator) {
  return customRef((track, trigger) => {
    let value = initialValue;
    let error = null;

    return {
      get() {
        track();
        return { value, error };
      },
      set(newValue) {
        try {
          const validationResult = validator(newValue);
          if (validationResult === true) {
            value = newValue;
            error = null;
          } else {
            error = validationResult;
          }
        } catch (err) {
          error = err.message;
        }
        trigger();
      },
    };
  });
}

// 使用示例
const age = useValidatedRef(18, (value) => {
  if (value < 0) return "年龄不能为负数";
  if (value > 150) return "年龄不能超过150岁";
  return true;
});

🎨 在模板中使用

<template>
  <div>
    <!-- 防抖输入框 -->
    <input v-model="searchQuery" placeholder="搜索..." />
    <p>搜索内容: {{ searchQuery }}</p>

    <!-- 异步数据 -->
    <div v-if="userData.loading">加载中...</div>
    <div v-else-if="userData.error">错误: {{ userData.error }}</div>
    <div v-else>{{ userData.value }}</div>

    <!-- 数据验证 -->
    <input v-model="age.value" type="number" />
    <p v-if="age.error" style="color: red;">{{ age.error }}</p>
  </div>
</template>

<script setup>
import { useDebouncedRef, useAsyncRef, useValidatedRef } from "./composables";

const searchQuery = useDebouncedRef("", 500);
const userData = useAsyncRef(() =>
  fetch("/api/user").then((res) => res.json())
);
const age = useValidatedRef(18, (value) => {
  if (value < 0) return "年龄不能为负数";
  return true;
});
</script>

⚠️ 注意事项

🔢 依赖收集和触发

  • ✅ 在 get() 函数中必须调用 track()
  • ✅ 在 set() 函数中必须调用 trigger()
  • ❌ 忘记调用会导致响应式失效

🕐 异步操作

  • ⚠️ 在 set() 中进行异步操作时要小心
  • 🔄 考虑使用 nextTick() 确保 DOM 更新

🧹 内存泄漏

  • 🗑️ 及时清理定时器和事件监听器
  • 🔄 在组件卸载时清理资源

🎯 最佳实践

1️⃣ 封装为组合式函数

将复杂的 customRef 逻辑封装为可复用的组合式函数。

2️⃣ 提供合理的默认值

为 customRef 提供合理的初始值,避免 undefined 状态。

3️⃣ 错误处理

在异步操作和验证逻辑中添加适当的错误处理。

4️⃣ 性能优化

避免在 getter 中进行昂贵的计算,考虑使用缓存机制。

❓ 常见问题

Q: customRef 和 computed 有什么区别?

A: customRef 提供完全的控制权,而 computed 是基于依赖的派生值。customRef 适合需要自定义 get/set 逻辑的场景。

Q: 可以在 customRef 中使用其他响应式数据吗?

A: 可以,但需要确保正确调用 track() 来收集依赖。

Q: customRef 是否支持深层响应式?

A: 默认不支持,需要手动处理嵌套对象的响应式。

📝 总结

customRef() 是 Vue 3 中实现复杂响应式逻辑的强大工具。它提供了完全的控制权,适用于防抖、本地存储同步、异步数据加载等场景。通过合理使用 track()trigger() 函数,可以创建高效且灵活的响应式数据。在开发中,建议将复杂的 customRef 逻辑封装为组合式函数,以提高代码的可复用性和可维护性。

原文链接:https://code.ifrontend.net/archives/1067,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?