了解VueUse

8,458 阅读9分钟

VueUse官网翻译解读

指南

立即开始

VueUse是基于Compomit API的实用程序函数的集合。在继续之前,我们假设您已经熟悉组合API的基本思想。

安装

🎩 VueUse 通过vue-demi的强大功能在单个包中适用于 Vue 2 和 3!

npm i @vueuse/core

Vue 3 演示: ViteWebpack / Vue 2 演示: Vue CLI

从 v6.0 开始,VueUse 需要 >= v3.2 或 >= v1.1vue``@vue/composition-api

CDN

<script src="https://unpkg.com/@vueuse/shared"></script>
<script src="https://unpkg.com/@vueuse/core"></script>

它将暴露在全局作用域window.VueUse

Nuxt

从 v6.7.0 开始,我们发布了一个 Nuxt 模块,用于自动导入 Nuxt 3 和 Nuxt Bridge。

// nuxt.config.js
export default {
  buildModules: [
    '@vueuse/core/nuxt'
  ]
}

然后在Nuxt应用程序中的任何位置使用VueUse功能。例如:

<script setup lang="ts">
const { x, y } = useMouse()
</script>

<template>
  <div>pos: {{x}}, {{y}}</div>
</template>

用法示例

只需从中导入所需的功能@vueuse/core

import { useMouse, usePreferredDark, useLocalStorage } from '@vueuse/core'

export default {
  setup() {
    // 跟踪鼠标位置
    const { x, y } = useMouse()

    // 用户喜欢黑色主题吗
    const isDark = usePreferredDark()

    // 在localStorage中持久化状态
    const store = useLocalStorage(
      'my-storage', 
      {
        name: 'Apple',
        color: 'red',
      },
    )

    return { x, y, isDark, store }
  }
}

有关更多详细信息,请参阅函数列表

最佳实践

解构

VueUse 中的大多数函数都返回一个refs 对象,您可以使用ES6 的对象析构语法来获取所需的内容。例如:

import { useMouse } from '@vueuse/core'

// “x”和“y”是引用
const { x, y } = useMouse()

console.log(x.value)

const mouse = useMouse()

console.log(mouse.x.value)

如果您希望将它们用作对象属性样式,则可以使用 打开 ref 的包装。例如:reactive()

import { reactive } from 'vue' 
import { useMouse } from '@vueuse/core'

const mouse = reactive(useMouse())

// "x"和"y"将自动展开, 不需要 `.value` 
console.log(mouse.x)

配置

这些显示了 VueUse 中大多数函数的常规配置。

事件过滤器

从 v4.0 开始,我们提供了事件过滤器系统,以便灵活地控制何时触发事件。例如,您可以使用 并控制事件触发速率:throttleFilter``debounceFilter

import { throttleFilter, debounceFilter, useLocalStorage, useMouse } from '@vueuse/core'

// 更改将写入localStorage,并设置1s
const storage = useLocalStorage('my-key', { foo: 'bar' }, { eventFilter: throttleFilter(1000) })

// 鼠标位置将在鼠标空闲100ms后更新
const { x, y } = useMouse({ eventFilter: debounceFilter(100) })

此外,您可以利用暂时暂停某些事件。pausableFilter

import { pausableFilter, useDeviceMotion } from '@vueuse/core'

const motionControl = pausableFilter()

const motion = useDeviceMotion({ eventFilter: motionControl.eventFilter })

motionControl.pause() 

// 运动更新停顿了一下

motionControl.resume()

// 运动更新恢复

无功时序

VueUse 的功能遵循 Vue 的反应性系统默认值,尽可能进行冲洗计时

对于类似 -like 的可组合对象(例如,每当使用时暂停监视存储使用 RefHistory,默认值为 。这意味着它们将缓冲无效的效果并异步刷新它们。这可以避免在同一"tick"中发生多个状态突变时不必要的重复调用。watch``{ flush: 'pre' }

与 使用 的方式相同,VueUse 允许您通过传递以下选项来配置计时:watch``flush

const { pause, resume } = pausableWatch(
  () => {
    // 安全访问更新后的DOM
  },
  { flush: 'post' }
)

刷新选项(默认:"pre")

  • 'pre':在同一个"勾号"中缓冲无效效果,并在渲染之前刷新它们
  • 'post':像"pre"一样的异步,但在组件更新后触发,以便您可以访问更新的DOM
  • 'sync':强制效果始终同步触发

**注意:**对于类似可组合物(例如syncRef controlledComputed,当刷新计时可配置时,默认值更改为将它们与 Vue 中计算的引用的工作方式保持一致。computed``{ flush: 'sync' }

可配置的全局依赖关系

在 v4.0 中,访问浏览器 API 的函数将提供一个选项字段,供您指定全局依赖项(例如 、和 )。默认情况下,它将使用全局实例,因此在大多数情况下,您无需担心它。在使用 iframe 和测试环境时,此配置非常有用。window``document``navigator

// 访问父上下文中
const parentMousePos = useMouse({ window: window.parent })

const iframe = document.querySelect('#my-iframe')

// 接触子上下文
const childMousePos = useMouse({ window: iframe.contextWindow })
// 测试
const mockWindow = /* ... */

const { x, y } = useMouse({ window: mockWindow })

组件

在 v5.0 中,我们引入了一个新包,提供可组合函数的无渲染组件样式用法。@vueuse/components

例如onClickOutside而不是

<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'

const el = ref()

function close () {
  /* ... */
}

onClickOutside(el, close)
</script>

<template>
  <div ref="el">
    Click Outside of Me
  </div>
</template>

现在,您可以以组件方式使用它:

<script setup>
import { OnClickOutside } from '@vueuse/components'

function close () {
  /* ... */
}
</script>

<template>
  <OnClickOutside @trigger="close">
    <div>
      Click Outside of Me
    </div>
  </OnClickOutside>
</template>

同样,您也可以使用 以下命令访问返回值:v-slot

<UseMouse v-slot="{ x, y }">
  x: {{ x }}
  y: {{ y }}
</UseMouse>
<UseDark v-slot="{ isDark, toggleDark }">
  <button @click="toggleDark()">
    Is Dark: {{ isDark }}
  </button>
</UseDark>

安装

$ npm i @vueuse/core @vueuse/components

有关组件样式的详细用法,请参阅每个函数的文档。

贡献

感谢您有兴趣为这个项目做出贡献!

发展#

设置

将此存储库克隆到本地计算机并安装依赖项。

pnpm install

我们使用 Vitepress 进行快速开发和记录。您可以通过以下方式在本地启动它

pnpm dev

贡献#

现有功能#

随意增强现有功能。请尽量不要引入重大更改。

新功能#

有关添加新函数的一些注意事项

  • 在开始工作之前,最好先打开一个问题进行讨论。
  • 实现应作为文件夹放置在下,并在packages/core``index.ts
  • 在软件包中,尽量不要引入第三方依赖项,因为此软件包旨在尽可能轻量级。core
  • 如果您想引入第三方依赖项,请为@vueuse/集成做出贡献或创建新的附加组件。
  • 您可以在 函数文件夹 部分所述的 函数模板 下找到函数模板。packages/core/_template/
  • 在为函数编写文档时,and 将在生成时自动更新,因此不要觉得需要更新它们。<!--FOOTER_STARTS-->``<!--FOOTER_ENDS-->

请注意,您不需要更新 或 软件包的 。它们是自动生成的。indexes.json``index.ts

新的附加组件#

非常欢迎新的附加组件!

  • 在 下创建一个新文件夹,将其命名为您的加载项名称。packages/
  • 在 中添加附加组件详细信息scripts/packages.ts
  • 在该文件夹下创建。README.md
  • 像向核心包中添加功能一样添加函数。
  • 作为 PR 提交和提交。

项目结构#

莫诺雷波#

我们将 monorepo 用于多个软件包

packages
  shared/         - shared utils across packages
  core/           - the core package
  firebase/       - the Firebase add-on
  [...addons]/    - add-ons named

函数文件夹#

函数文件夹典型值包含以下 4 个文件:

您可以在下找到模板packages/core/_template/

index.ts            # function source code itself
demo.vue            # documentation demo
index.test.ts       # jest unit testing
index.md            # documentation

因为您应该导出带有名称的函数。index.ts

// DO
export { useMyFunction }

// DON'T
export default useMyFunction

因为第一句话将显示为函数列表中的简短介绍,因此请尽量保持简短明了。index.md

# useMyFunction

This will be the intro. The detail descriptions...

阅读有关指南的更多信息。

代码样式#

只要安装开发依赖项,就不必担心代码样式。Git 钩子将在提交时为您格式化和修复它们。

谢谢

再次感谢您对这个项目感兴趣!你真棒!

指引

以下是 VueUse 函数的指南。您还可以将它们作为创作自己的可组合函数或应用程序的参考。

你还可以找到这些设计决策的一些原因,以及一些编写可组合函数的技巧,以及Anthony Fu关于VueUse的演讲:

常规

  • 从以下位置导入所有 Vue API"vue-demi"
  • 尽可能使用ref代替reactive
  • 尽可能使用选项对象作为参数,以便为将来的扩展提供更灵活的操作。
  • 当包装大量数据时,使用shallowRef代替ref
  • 在使用全局变量时使用configurableWindow(等等),比如window可以灵活地处理多个窗口、测试模拟和SSR。
  • 当涉及尚未由浏览器广泛实现的 Web API 时,还会输出标志isSupported
  • 在内部使用watchwatchEffect时,也要尽可能配置immediateflush选项
  • 使用tryOnUnmounted来优雅地清除副作用
  • 避免使用控制台日志

另请阅读:最佳实践

浅引用

当包装大量数据时,使用shallowRef代替ref

export function useFetch<T>(url: MaybeRef<string>) {
  // 使用' shallowRef '来防止深层反应
  const data = shallowRef<T | undefined>()
  const error = shallowRef<Error | undefined>()

  fetch(unref(url))
    .then(r => r.json())
    .then(r => data.value = r)
    .catch(e => error.value = e)

  /* ... */
}

可配置的全局变量

当使用全局变量如windowdocument时,在选项界面中支持configurableWindowconfigurableDocument,以使该函数在多窗口、测试模拟和SSR等场景时更加灵活。

了解有关实施的更多信息:_configurable.ts

import { ConfigurableWindow, defaultWindow } from '../_configurable'

export function useActiveElement<T extends HTMLElement>(
  options: ConfigurableWindow = {}
) {
  const {
    // defaultWindow = isClient ? window : undefined
    window = defaultWindow
  } = options

  let el: T
  
  // 在Node.js环境(SSR)中跳过
  if (window) {
    window.addEventListener('blur', () => {
      el = window?.document.activeElement
    }, true)
  }

  /* ... */
}

使用示例:

// 在iframe和绑定到父窗口
useActiveElement({ window: window.parent })

Watch选项

在内部使用watchwatchEffect时,也要尽可能配置立即和刷新选项。例如debouncedWatch

import { WatchOptions } from 'vue-demi'

// 扩展watch选项
export interface DebouncedWatchOptions extends WatchOptions {
  debounce?: number
}

export function debouncedWatch(
  source: any,
  cb: any,
  options: DebouncedWatchOptions = {},
): WatchStopHandle {
  return watch(
    source,
    () => /* ... */,
    options, // 通过watch选择
  )
}

控制

我们使用controls选项,允许用户使用具有单个返回功能的简单用法,同时能够在需要时拥有更多的控制和灵活性。阅读更多: #362.

何时提供controls选项#

// 常见的使用
const timestamp = useTimestamp()

// 更多的灵活性控制
const { timestamp, pause, resume } = useTimestamp({ controls: true })

请参阅useTimestamp的源代码,以实现适当的 TypeScript 支持。

何时不提供controls选项#

const { pause, resume } = useRafFn(() => {})

isSupported标志#

当涉及到尚未被浏览器广泛实现的Web api时,也输出isSupported标志。

例如 useShare

export function useShare(
  shareOptions: MaybeRef<ShareOptions> = {},
  options: ConfigurableNavigator = {}
) {
  const { navigator = defaultNavigator } = options
  const isSupported = navigator && 'canShare' in navigator

  const share = async(overrideOptions) => {
    if (isSupported) {
      /* ...implementation */ 
    }
  }

  return {
    isSupported,
    share,
  }
}

Renderless组件#

  • 使用渲染函数而不是 Vue SFC
  • 将道具包裹reactive`中,以便轻松地将它们作为道具传递到插槽中
  • 更喜欢将函数选项用作属性类型,而不是自己重新创建它们
  • 仅当函数需要目标绑定到时,才将槽包装在 HTML 元素中
import { defineComponent, reactive } from 'vue-demi'
import { useMouse, MouseOptions } from '@vueuse/core'

export const UseMouse = defineComponent<MouseOptions>({
  name: 'UseMouse',
  props: ['touch', 'resetOnTouchEnds', 'initialValue'] as unknown as undefined,
  setup(props, { slots }) {
    const data = reactive(useMouse(props))

    return () => {
      if (slots.default)
        return slots.default(data)
    }
  },
})

有时一个函数可能有多个参数,在这种情况下,您可能需要创建一个新接口,以将所有接口合并到组件属性的单个接口中。

import { useTimeAgo, TimeAgoOptions } from '@vueuse/core'

interface UseTimeAgoComponentOptions extends Omit<TimeAgoOptions<true>, 'controls'> {
  time: MaybeRef<Date | number | string>
}

export const UseTimeAgo = defineComponent<UseTimeAgoComponentOptions>({...})