vue3中customRef的用法以及使用场景

1. 基本概念

customRef 是 Vue3 提供的用于创建自定义响应式引用的 API,允许显式地控制依赖追踪和触发响应。它返回一个带有 getset 函数的工厂函数来自定义 ref 的行为。

1.1 基本语法

js 复制代码
import { customRef } from 'vue'

function createCustomRef(value) {
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 追踪依赖
        return value
      },
      set(newValue) {
        value = newValue
        trigger() // 触发更新
      }
    }
  })
}

2. 常见使用场景

2.1 防抖 Ref

js 复制代码
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)
      }
    }
  })
}

// 使用示例
const searchQuery = useDebouncedRef('', 500)

// 在模板中使用
// <input v-model="searchQuery" />

2.2 节流 Ref

js 复制代码
function useThrottledRef(value, delay = 200) {
  let lastTriggerTime = 0
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        const now = Date.now()
        if (now - lastTriggerTime >= delay) {
          value = newValue
          lastTriggerTime = now
          trigger()
        }
      }
    }
  })
}

// 使用示例
const scrollPosition = useThrottledRef(0, 100)

2.3 验证 Ref

js 复制代码
function useValidatedRef(value, validator) {
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        if (validator(newValue)) {
          value = newValue
          trigger()
        } else {
          console.warn('Invalid value:', newValue)
        }
      }
    }
  })
}

// 使用示例
const age = useValidatedRef(18, (value) => {
  return Number.isInteger(value) && value >= 0 && value <= 120
})

2.4 异步 Ref

js 复制代码
function useAsyncRef(getter) {
  let value = null
  let isLoading = true
  
  const ref = customRef((track, trigger) => {
    // 初始加载数据
    getter().then(data => {
      value = data
      isLoading = false
      trigger()
    })
    
    return {
      get() {
        track()
        return { value, isLoading }
      },
      set() {
        throw new Error('Async ref is readonly')
      }
    }
  })
  
  return ref
}

// 使用示例
const userProfile = useAsyncRef(async () => {
  const response = await fetch('/api/user')
  return response.json()
})

3. 高级应用场景

3.1 持久化 Ref

js 复制代码
function useLocalStorageRef(key, defaultValue) {
  const storedValue = JSON.parse(localStorage.getItem(key) || 'null')
  let value = storedValue !== null ? storedValue : defaultValue
  
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        value = newValue
        localStorage.setItem(key, JSON.stringify(newValue))
        trigger()
      }
    }
  })
}

// 使用示例
const theme = useLocalStorageRef('app-theme', 'light')

3.2 格式化 Ref

js 复制代码
function useFormattedRef(value, formatter, parser) {
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return formatter(value)
      },
      set(newValue) {
        value = parser(newValue)
        trigger()
      }
    }
  })
}

// 使用示例
const price = useFormattedRef(
  1000,
  (value) => `$${value.toFixed(2)}`,
  (value) => parseFloat(value.replace('$', ''))
)

4. 实际应用示例

4.1 表单输入处理

vue 复制代码
<template>
  <div>
    <input v-model="email" />
    <p>状态: {{ email.status }}</p>
    <p>错误信息: {{ email.error }}</p>
  </div>
</template>

<script setup>
function useValidatedEmailRef(initialValue = '') {
  let value = initialValue
  let status = 'initial'
  let error = ''
  
  const validateEmail = (email) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return regex.test(email)
  }
  
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return {
          value,
          status,
          error
        }
      },
      set(newValue) {
        value = newValue
        if (!newValue) {
          status = 'initial'
          error = ''
        } else if (validateEmail(newValue)) {
          status = 'valid'
          error = ''
        } else {
          status = 'invalid'
          error = '请输入有效的邮箱地址'
        }
        trigger()
      }
    }
  })
}

const email = useValidatedEmailRef()
</script>

4.2 搜索优化

vue 复制代码
<template>
  <div>
    <input v-model="searchQuery" />
    <div v-if="searchQuery.isLoading">加载中...</div>
    <ul v-else>
      <li v-for="result in searchQuery.results" :key="result.id">
        {{ result.title }}
      </li>
    </ul>
  </div>
</template>

<script setup>
function useSearchRef(initialValue = '') {
  let value = initialValue
  let results = []
  let isLoading = false
  
  const performSearch = async (query) => {
    if (!query) {
      results = []
      return
    }
    
    isLoading = true
    try {
      const response = await fetch(`/api/search?q=${query}`)
      results = await response.json()
    } catch (error) {
      console.error('Search failed:', error)
      results = []
    } finally {
      isLoading = false
    }
  }
  
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return {
          value,
          results,
          isLoading
        }
      },
      set(newValue) {
        value = newValue
        performSearch(newValue).then(() => trigger())
      }
    }
  })
}

const searchQuery = useSearchRef()
</script>

5. 最佳实践

5.1 性能优化

js 复制代码
// 避免不必要的触发
function useOptimizedRef(value) {
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        // 只在值真正改变时触发更新
        if (value !== newValue) {
          value = newValue
          trigger()
        }
      }
    }
  })
}

5.2 错误处理

js 复制代码
function useSafeRef(value, errorHandler = console.error) {
  return customRef((track, trigger) => {
    return {
      get() {
        try {
          track()
          return value
        } catch (error) {
          errorHandler(error)
          return null
        }
      },
      set(newValue) {
        try {
          value = newValue
          trigger()
        } catch (error) {
          errorHandler(error)
        }
      }
    }
  })
}

6. 注意事项

  1. 避免过度使用
js 复制代码
// ❌ 不要为简单的值使用 customRef
const simpleValue = customRef((track, trigger) => ({
  get() {
    track()
    return value
  },
  set(newValue) {
    value = newValue
    trigger()
  }
}))

// ✅ 使用普通的 ref
const simpleValue = ref(value)
  1. 保持响应性
js 复制代码
// 确保在需要的时候调用 track 和 trigger
function useCustomRef(value) {
  return customRef((track, trigger) => ({
    get() {
      track() // 不要忘记 track
      return value
    },
    set(newValue) {
      value = newValue
      trigger() // 不要忘记 trigger
    }
  }))
}
  1. 内存管理
js 复制代码
// 清理副作用
function useCustomRef(value) {
  let cleanup = null
  
  return customRef((track, trigger) => ({
    get() {
      track()
      return value
    },
    set(newValue) {
      // 清理之前的副作用
      if (cleanup) {
        cleanup()
      }
      
      value = newValue
      // 设置新的副作用
      cleanup = setupSideEffect(value)
      trigger()
    }
  }))
}
相关推荐
wl851118 分钟前
Vue 入门到实战 七
前端·javascript·vue.js
Enti7c1 小时前
用 HTML、CSS 和 JavaScript 实现抽奖转盘效果
前端·css
LCG元1 小时前
Vue.js组件开发-使用Vue3如何实现上传word作为打印模版
前端·vue.js·word
dal118网工任子仪1 小时前
94,【2】buuctf web [安洵杯 2019]easy_serialize_php
android·前端·php
大模型铲屎官2 小时前
HTML5 技术深度解读:本地存储与地理定位的最佳实践
前端·html·html5·本地存储·localstorage·地理定位·geolocation api
一 乐2 小时前
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端·船运系统
m0_528723813 小时前
在React中使用redux
前端·javascript·react.js
谦谦橘子3 小时前
手把手教你实现一个富文本
前端·javascript
Future_yzx3 小时前
Java Web的发展史与SpringMVC入门学习(SpringMVC框架入门案例)
java·前端·学习