vueuse最佳实践——创建更有灵活性的组合式函数和使用更优雅的方式进行编码

问题场景:我们想要组织代码的方式更加简洁。希望代码会进行如下转换,并且效果一致,同时希望我们创建的组合式函数具有更多的灵活性。现在具有优化前的代码和优化后的代码如下:

优化前

js 复制代码
    const isDark = useDark() // 切换主题时,isDark的value会变化 
    const title = useTitle('hello') // 这个函数修改document.title的值 
    
    console.log(document.title) // hello 
    
    // 一个再常见不过的处理方式。
    watch(isDark, ()=>{ 
        // title是ref 
        title.value = isDark.value? 'good evening' : 'good morning' 
    })

优化后

js 复制代码
const isDark = useDark()
const title = useTitle (()=>isDark.value? 'good evening' : 'good morning')

但是问题是优化后的代码传入的是一个getter函数,这需要一些特殊的处理,而且如果是传递响应式对象属性参数,又应该怎么保持跟响应式对象之间的连接,而不因为reactive方法失去响应性呢? 所以我们这里分成两个情况讨论,一个是如何将一个函数改造成支持getter函数的组合式函数。另一个是这种编码的方式的如何迭代形成的。

问题:创建更有灵活性的组合式函数

问题场景,假如我们有些通用的工具函数,比如useDouble。函数的参数是数值类型,返回值同样也是数值类型。现在由于是一个compositionApi,我希望返回的是一个响应式对象

now:

js 复制代码
function useDouble(t){
  return t*2
}
const count = ref(0)

const result = computed(()=>{
 return useDouble(count.value)
})
</script>

<template>
  <button @click="count++">count+</button>
  <h1>{{ result }}</h1>
 
</template>

after: 现在我们返回一个计算属性来获取响应性变量ref,但是我们需要参数必须是一个ref,如果传递的是一个原始number值,那么将会出现NaN

ts 复制代码
// 现在我们返回的是一个计算属性,但是我们需要参数必须是一个ref
function useDouble(t:Ref<T>){
  return computed(()=>t.value*2)
}
const count = ref(0)

const result = useDouble(count)
</script>

<template>
  <button @click="count++">count+</button>
  <h1>{{ result }}</h1>
 
</template>

我们希望我们能传递一个原始值,以增强这个函数的灵活性,所以需要再次进行一个改进

ts 复制代码
//现在支持原始值传入,会返回一个计算属性ref。
function useDouble(t:Ref<T>|T){
  // 这里可以替换为computed(()=>(unref(t)*2)
  return computed(()=>(isRef(t)?t.value:t)*2)
}

由于我们不清楚计算属性中的t是ref还是普通的原始值类型。我们希望可以更统一的进行操作。于是我们可以将t规范化为ref

ts 复制代码
//现在支持原始值传入,会返回一个计算属性ref。
function useDouble(t:Ref<T>|T){
  const r = ref(t)
  return computed(()=>r.value*2)
}

这样仍然有一个缺点,就是我们使用reactive的属性传入该函数的时候,由于reactive方法的局限性,我们将失去响应式对象跟返回的计算属性的连接。这样页面只有方法传递响应式对象第一次的值作为参数,获取到返回的计算属性,从而渲染出来的结果。对目前的结果来说,就是2,且无法通过button增加(失去连接)。

js 复制代码
<script setup>
function useDouble(t){
  const r = ref(t)
  return computed(()=>r.value*2)
}
const count = reactive({data:1})
const result = useDouble(count.data)
</script>

<template>
  <button @click="count.data++">count+</button>
  <h1>{{ result }}</h1>
 
</template>

进行改造,让函数内部处理getter函数,这样能够让参数在传递reactive对象的属性的时候,也能保持连接,所以目前的useDouble函数就能支持原始值、ref、getter函数传参,跟watch函数一样。

js 复制代码
<script setup>
function useDouble(t){
  // const r = ref(t)
  const r = typeof t === 'function' ? computed(t) : ref(t) // after
  return computed(()=>r.value*2)
}
const count = reactive({data:1})
const result = useDouble(()=>count.data) // after
</script>

<template>
  <button @click="count.data++">count+</button>
  <h1>{{ result }}</h1>
 
</template>

接着我们再进行refactor,将转换的部分抽取出来,使其成为一个函数: resolveRef

js 复制代码
const resolveRef = (t) => typeof t === 'function' ? computed(t) : ref(t) // after

现在我们就可以根据resolveRef创建一个ref或者computedRef,始终对依赖的响应式对象有连接。

改造完成之后的好处是这个useDouble函数的灵活性,在这样改造之后获得了很大的提升。无论是原始值,或者是ref,或者是响应式对象的属性相关的getter函数,都可以通过这个函数获取到一个ref,从而获得响应性。

编码方式的迭代

回到我们刚接触问题的时候。之前我们大概会这么进行编码,当用户切换主题时,isDark的值将发生变化。

js 复制代码
const isDark = useDark() // 切换主题时,isDark的value会变化
const title = useTitle('hello') // 这个函数修改document.title的值

console.log(document.title) // hello

watch(isDark, ()=>{
    // title是ref
    title.value = isDark.value? 'good evening' : 'good morning'
})

那么我们从上面的代码知道,title的值需要依赖isDark的值,如果useTitle的值支持ref传参,那我们将直接通过以下这种方式进行传参,于是useTitle方法内部获取了一个计算属性,直接通过该ref的变化,可以修改传出来的title的值。

js 复制代码
const isDark = useDark()
const title = useTitle(computed(()=>isDark.value? 'good evening' : 'good morning'))

缺点是我们需要手动调用computed进行包装

所以我们可以应用resolveRef来改造。于是在函数内部通过resolveRef保持了对isDark的连接。此时即使使用reactive也能够同样使用相同的编码。并且去除了computed的包装。在函数内部通过computed来收集依赖。

js 复制代码
const isDark = useDark()
const title = useTitle (()=>isDark.value? 'good evening' : 'good morning')

总结

我们通过resolveRef来统一化参数,并且在方法内部对ref进行了统一的处理。增加了组合式函数的灵活性。同时使用一种新的写法,来提高开发效率,减少出错的情况。

tips 缺点则是组合式函数依赖的ref,如果使用getter的话,就无法更新了,因为会被resolveRef转换为computedRef了。

相关推荐
掘金安东尼5 分钟前
⏰前端周刊第 453 期(2026年2月9日-2月15日)
前端·javascript·面试
Amumu121388 分钟前
CSS进阶导读
前端·css
Wcowin10 分钟前
为Zensical添加 GitHub 仓库卡片
javascript·github·zensical
anyup11 分钟前
uniapp开发的鸿蒙应用上架后,竟然月入4000+
前端·vue.js·harmonyos
无尽的沉默15 分钟前
使用Thymeleaf配置国际化页面(语言切换)
前端·spring boot
代码老中医27 分钟前
接手老项目的一个月,我重构了那个2000行的“祖传”React组件
前端
飘逸飘逸38 分钟前
Autojs进阶前言
android·javascript
叫我一声阿雷吧1 小时前
JS 入门通关手册(01)|零基础入门:JavaScript 到底是什么?学完能干嘛?
javascript·前端入门·js入门
用户83040713057011 小时前
外链跳转后首页参数丢失:从缓存兜底到页面重加载的完整方案
vue.js
用户83040713057011 小时前
路由传参刷新丢失问题:三种解决方案与最佳实践
前端