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了。

相关推荐
四喜花露水10 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy19 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust1 小时前
css:基础
前端·css
帅帅哥的兜兜1 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园1 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称1 小时前
购物车-多元素组合动画css
前端·css
编程一生2 小时前
回调数据丢了?
运维·服务器·前端
丶21362 小时前
【鉴权】深入了解 Cookie:Web 开发中的客户端存储小数据
前端·安全·web