关于vue.js:浅谈VueUse设计与实现

8次阅读

共计 5241 个字符,预计需要花费 14 分钟才能阅读完成。

前言

大家好, 我是 webfansplz. 首先跟大家分享一个好消息, 我退出 VueUse 团队啦, 感激 @antfu 的邀请, 很开心成为团队的一员. 明天跟大家聊聊 VueUse 的设计与实现.

介绍

大家都晓得 Vue3 引入了组合式 API, 大大晋升了逻辑复用能力.VueUse 基于组合式 API 实现了很多易用、实用且乏味的性能. 比方:

useMagicKeys

useMagicKeys 监听按键状态, 并提供了组合热键的性能, 十分的神奇和乏味. 应用它, 咱们能够很容易的监听咱们应用 CV 大法的次数 :)

useScroll

useScroll 提供了一些响应式的状态和值, 比方滚动状态、到达状态、滚动方向以及以后滚动地位.

useElementByPoint

useElementByPoint 用于实时获取以后坐标地位最顶层的元素, 配合 useMouse, 咱们能够做一些乏味的交互和成果.

用户体验

使用者体验

VueUse 无论是面向使用者还是开发者都做到了很棒的用户体验. 咱们先来看看使用者体验:

强类型反对

VueUse 采纳了 TypeScript 进行编写并且带有残缺的 TS 文档, 有良好的 TS 反对能力.

SSR 反对

咱们对 SSR 进行了敌对的反对, 它能够在服务端渲染场景工作的很好.

易用性

一些反对传入配置选项的函数咱们会为使用者提供一套罕用的默认选项, 这样能够保障用户在大多数利用 场景下并不需要过多的关注你的性能实现和细节. 以 useScroll 为例:

<script setup lang="ts">
import {useScroll} from '@vueuse/core'

const el = ref<HTMLElement | null>()
// 只需传入滚动元素就能够工作
const {x, y} = useScroll(el)
// 节流反对选项
const {x, y} = useScroll(el, { throttle: 200})
</script>

useScroll 对一些有性能要求的开发者提供了节流选项. 然而咱们心愿的是用户有需要的时候才关注到有这个配置, 因为当配置参数一多的时候, 了解参数含意和配置其实是一种心智累赘. 另外, 通用默认配置其实也是开箱能力的一种体现 !

应用文档

应用文档咱们提供了可交互的 Demo 和精简的 Usage, 用户能够通过把玩 Demo 进一步理解性能, 也能够通过 CV 大法复制 Usage 很容易的就用上性能. 真香 !

兼容性

后面咱们提到了 Vue3 引入了组合式 API 的概念, 然而得益于 composition-api 插件的实现, 咱们也能在 Vue2 我的项目应用组合式 API. 为了让更多的用户可能应用 VueUse,Anthony Fu 实现了 vue-demi , 它通过判断用户装置环境 (Vue2 我的项目 援用 composition-api 插件,Vue3 我的项目援用官网包), 这样 Vue2 用户也能用上 VueUse 啦, 奈斯 !

开发者体验

目录构造

在基于 Monorepo 的根底上, 我的项目采纳了扁平化目录构造, 便于开发者查找相应的函数.

咱们为每个函数的实现创立了一个独立的文件夹, 这样开发者在修复 Bug 和新增性能的时候, 只须要关注该文件夹下具体函数的实现, 并不需要关注我的项目自身实现的细节, 大大降低了上手的老本.Demo 和文档的编写也在该文件夹下实现, 防止了高低重复横跳寻找目录构造文件的蹩脚研发体验.

奉献指南

咱们提供了十分具体的奉献指南帮忙想要奉献的开发者疾速开始并且编写了一些自动化脚本帮忙开发者防止一些手动的工作.

原子化 CSS

我的项目应用原子化 CSS 作为 CSS 的编写计划, 我集体感觉原子化 CSS 能够帮忙咱们疾速的编写出演示 Demo, 并且每个函数的 Demo 独立不耦合, 不会产生形象复用的心智累赘.

设计思维

可组合的函数

可组合的函数简略来说就是函数间能够建设组合关系, 举个例子:

useScroll 的实现组合了三个函数, 将一个个繁多职责的函数组合造成另一个函数, 达到逻辑复用的能力, 我感觉这也便是组合式函数的魅力所在吧. 当然, 每个函数也都能够进行独立应用, 用户能够依据本人的须要进行抉择.

开发者在解决性能函数的时候能够做到更好的关注点拆散, 比方解决 useScroll 时咱们只须要关注滚动性能的实现, 并不需要关注防抖节流及事件绑定外部的逻辑与实现.

建设 ” 连结 ”

Anthony Fu 在 Vue Conf 2021 中分享了这样一个模式:

  • 建设输出 -> 输入的连结
  • 输入会主动依据输出的扭转而扭转

咱们在编写可组合式函数的时候建设数据和逻辑的连结, 这样咱们就不必关怀如何更新数据, 什么时候更新. 举个例子:

<script setup lang="ts">
import {ref} from 'vue'
import {useDateFormat, useNow} from '@vueuse/core'

const now = useNow() // 返回一个 ref 值
const formatted = useDateFormat(now) // 将数据传入与逻辑建设连结

</script>

// useDateFormat 实现
function useDateFormat(date, formatStr = 'HH:mm:ss') {return computed(() => formatDate(normalizeDate(unref(date)), unref(formatStr)))
}

从下面这个例子中咱们能够看出, useDateFormat 在外部逻辑中应用了计算属性对输出进行包裹, 这样咱们就能够做到输入主动依据输出扭转而扭转, 而用户只需传入一个响应式值, 不须要关注具体更新逻辑.

尽可能应用 ref 代替reactive

refreactive 有各自的优缺点, 这里次要从用户角度谈谈我集体的认识 :

// reactive

function useScroll(element){const position = reactive({ x: 0, y: 0});
  // impl...
  return {position,...}
}
// 解构失落响应性
const {position} = useScroll(element)
// 用户需手动 toRefs 放弃响应性
const {x, y} = toRefs(position)

// ref

function useScroll(element){const x = ref(0);
  const y = ref(0);
  // impl...
  return {x,y,...}
}
// 不会失落响应性, 用户可间接拿来渲染,watch..
const {x, y} = useScroll(element)

从下面这个例子中咱们能够看到, 如果咱们应用 reactive 的话, 用户须要思考解构会失落响应性的问题, 这也从肯定水平上限度了用户应用解构的自由度和升高了这个函数的易用性.

可能有的人会吐槽 ref.value应用, 其实在大多数状况下, 咱们能够通过一些技巧缩小它的应用:

  • unref API
const x = ref(0)
console.log(unref(x)) // 0
  • 应用 reactive 解包ref
const x = ref(0)
const y = ref(0)
const position = reactive({x, y})
console.log(position.x, position.y) // 0 0
  • 还在试验阶段的Reactivity Transform
<script setup>
let count = $ref(0)
count++
</script>

应用 options 对象作为参数

在实现一个函数时, 如果有选项参数的场景, 咱们通常倡议开发者应用对象来作为入参, 举个例子 :

// good

function useScroll(element, { throttle, onScroll, ...}){...}

// bad

function useScroll(element, throttle, onScroll, ....){...}

大家能够很清晰的看到两者的区别, 毫无疑问第一种写法的扩展性会更强, 在之后迭代中也不容易对性能自身造成一些破坏性的改变.

文档实现

对于函数的具体实现就不细说了, 毕竟咱们有 200 个那么多 😝 . 这里跟大家分享一下 VueUse 构建文档局部比拟有意思的实现, 我感觉做的很棒.

文档组成

咱们先来看下一个性能函数文档的组成部分 :

构建流程

VueUse 应用了 VitePress 作为文档构建工具, 上面咱们来看下比拟有意思的局部:

  • 以 packages 文件夹为入口启动 VitePress 服务

VitePress 应用了约定式路由 (文件即路由), 所以拜访 http://xxx.com/core/onClickOutside 实际上就会解析咱们对应的 index.md 文件. 看到这里大家就会有疑难了,index.md文件里只蕴含了 usage 啊, 其余的信息是哪里来的呢 ? 乏味的局部来了~

  • 编写 Vite 插件 MarkdownTransform对 Markdown 文件进行解决 :
export function MarkdownTransform(): Plugin {
 
  return {
    name: 'vueuse-md-transform',
    enforce: 'pre',
    async transform(code, id) {if (!id.endsWith('.md'))
        return null

      const [pkg, name, i] = id.split('/').slice(-3)

      if (functionNames.includes(name) && i === 'index.md') {
        // 对 index.md 进行解决
        // 应用拼接字符串的形式拼接 Demo, 类型申明, 贡献者信息和更新日志
        const {footer, header} = await getFunctionMarkdown(pkg, name)

        if (hasTypes)
          code = replacer(code, footer, 'FOOTER', 'tail')
        if (header)
          code = code.slice(0, sliceIndex) + header + code.slice(sliceIndex)
      }

      return code
    },
  }
}

通过这个 Vite 插件的解决, 咱们的文档局部就残缺了. 这里又有一个疑难, 贡献者的数据和更新日志数据是怎么来的呢 ? 这两个数据处理的形式都差不多, 我就拿其中一个来阐明实现 :

  • 获取 git 提交者信息
import Git from 'simple-git'

export async function getContributorsAt(path: string) {const list = (await git.raw(['log', '--pretty=format:"%an|%ae"','--', path]))
      .split('\n')
      .map(i => i.slice(1, -1).split('|') as [string, string])
    return list
}

咱们通过 simple-git 插件读取到相干文件提交者的信息, 有了数据之后, 那么咱们怎么将它们渲染到页面中呢 ? 还是应用 Vite 插件, 不过这次咱们要做的是注册虚构模块.

  • 注册虚构模块
const ID = '/virtual-contributors'

export function Contributors(data: Record<string, ContributorInfo[]>): Plugin {
  return {
    name: 'vueuse-contributors',
    resolveId(id) {return id === ID ? ID : null},
    load(id) {if (id !== ID) return null
      return `export default ${JSON.stringify(data)}`
    },
  }
}

将咱们方才获取到的数据在注册虚构模块的时候传入就能够了, 接下来咱们就能够在组件中引入虚构模块对数据进行拜访.

  • 应用数据
<script setup lang="ts">
import _contributors from '/virtual-contributors'
import {computed} from 'vue'

const props = defineProps<{fn: string}>()

const contributors = computed(() => _contributors[props.fn] || [])
</script>

拿到数据后, 咱们就能够进行页面渲染了. 这就是文档中 Contributors 和 Changelog 局部的实现原理. 咱们来看下成果 :

看完这个是不是感觉还蛮有意思的,Vite 插件其实还是能够用来搞很多事件的.

V8.0 来啦 🎉

咱们在前两天正式公布了 V8.0, 次要带来了:

  • 对一些函数的命名进行了规范化, 并应用别名做了向下兼容
  • 新增了几个函数,目前函数数量达到了 200 +
  • @vueuse/core/nuxt => @vueuse/nuxt
  • 对一些函数做了指令反对, 欢送应用

结语

最初, 感激 Anthony Fu 对本文的斧正和倡议, 瑞思拜 ! 如果我的文章对你有帮忙, 欢送关注我一起学习.

正文完
 0