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

相关推荐
咬人喵喵17 小时前
CSS Flexbox:拥有魔法的排版盒子
前端·css
LYFlied17 小时前
TS-Loader 源码解析与自定义 Webpack Loader 开发指南
前端·webpack·node.js·编译·打包
yzp011217 小时前
css收集
前端·css
暴富的Tdy17 小时前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok17 小时前
Web Worker
前端·javascript·vue.js
elangyipi12317 小时前
JavaScript 高级错误处理与 Chrome 调试艺术
开发语言·javascript·chrome
风舞红枫17 小时前
前端可配置权限规则案例
前端
前端不太难17 小时前
RN Navigation vs Vue Router:从架构底层到工程实践的深度对比
javascript·vue.js·架构
zhougl99618 小时前
前端模块化
前端
暴富暴富暴富啦啦啦18 小时前
Map 缓存和拿取
前端·javascript·缓存