规范指南
是 VueUse 函数的规范指南
指南
以下是 VueUse 函数的指南。您也可以将它们作为编写自己的可组合函数或应用程序的参考。
您还可以从 Anthony Fu 关于 VueUse 的演讲 中找到这些设计决策的一些原因以及编写可组合函数的一些技巧:
- Composable Vue - VueDay 2021
- 可组合的 Vue - VueConf China 2021 (中文)
通用
- 从
"vue"导入所有 Vue API。 - 尽可能使用
ref而不是reactive。 - 尽可能使用选项对象作为参数,以便将来扩展更灵活。
- 尽可能优先使用
shallowRef而不是ref。 - 在需要深层响应式时,优先使用显式命名的
deepRef而不是ref。 - 当使用
window等全局变量时,使用configurableWindow(等) 以便在多窗口、测试模拟和 SSR 中工作时保持灵活性。 - 当涉及尚未被浏览器广泛实现的 Web API 时,也输出
isSupported标志。 - 当内部使用
watch或watchEffect时,尽可能使immediate和flush选项可配置。 - 使用
tryOnUnmounted优雅地清除副作用。 - 避免使用
console.log。 - 当函数是异步时,返回一个
PromiseLike。
另请阅读:最佳实践
ShallowRef
当封装大量数据时,使用 shallowRef 而不是 ref。
export function useFetch<T>(url: MaybeRefOrGetter<string>) {
// 使用 `shallowRef` 防止深度响应式
const data = shallowRef<T | undefined>()
const error = shallowRef<Error | undefined>()
fetch(toValue(url))
.then(r => r.json())
.then(r => data.value = r)
.catch(e => error.value = e)
/* ... */
}
可配置的全局变量
当使用 window 或 document 等全局变量时,在选项接口中支持 configurableWindow 或 configurableDocument,以使函数在多窗口、测试模拟和 SSR 等场景中保持灵活。
了解更多实现细节:_configurable.ts
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
import { useEventListener } from '../useEventListener'
export function useActiveElement<T extends HTMLElement>(
options: ConfigurableWindow = {},
) {
const {
// defaultWindow = isClient ? window : undefined
window = defaultWindow,
} = options
let el: T
// 在 Node.js 环境 (SSR) 中跳过
if (window) {
useEventListener(window, 'blur', () => {
el = window?.document.activeElement
}, true)
}
/* ... */
}
用法示例:
// 在 iframe 中并绑定到父窗口
useActiveElement({ window: window.parent })
Watch 选项
当内部使用 watch 或 watchEffect 时,尽可能使 immediate 和 flush 选项可配置。例如 watchDebounced:
import type { WatchOptions } from 'vue'
// 扩展 watch 选项
export interface WatchDebouncedOptions extends WatchOptions {
debounce?: number
}
export function watchDebounced(
source: any,
cb: any,
options: WatchDebouncedOptions = {},
): WatchStopHandle {
return watch(
source,
() => { /* ... */ },
options, // 传递 watch 选项
)
}
Controls
我们使用 controls 选项,允许用户通过单一返回值的函数进行简单使用,同时在需要时能够拥有更多的控制和灵活性。阅读更多: #362。
何时提供 controls 选项
- 函数更常用于单个
ref或 - 示例:
useTimestamp,useInterval
// 常用用法
const timestamp = useTimestamp()
// 更多控制,增加灵活性
const { timestamp, pause, resume } = useTimestamp({ controls: true })
有关正确 TypeScript 支持的实现,请参阅 useTimestamp 的源代码。
何时不提供 controls 选项
- 函数更常用于多个返回值
- 示例:
useRafFn,useRefHistory
const { pause, resume } = useRafFn(() => {})
isSupported 标志
当涉及尚未被浏览器广泛实现的 Web API 时,也输出 isSupported 标志。
例如 useShare:
export function useShare(
shareOptions: MaybeRef<ShareOptions> = {},
options: ConfigurableNavigator = {},
) {
const { navigator = defaultNavigator } = options
const isSupported = useSupported(() => navigator && 'canShare' in navigator)
const share = async (overrideOptions) => {
if (isSupported.value) {
/* ...implementation */
}
}
return {
isSupported,
share,
}
}
异步可组合函数
当一个可组合函数是异步的,例如 useFetch,最好从可组合函数返回一个 PromiseLike 对象,以便用户能够 await 该函数。这在 Vue 的 <Suspense> API 中特别有用。
- 使用
ref来确定函数何时应该解析,例如isFinished。 - 将返回状态存储在变量中,因为它必须返回两次,一次在返回值中,一次在 Promise 中。
- 返回类型应该是返回类型和
PromiseLike的交集,例如UseFetchReturn & PromiseLike<UseFetchReturn>。
export function useFetch<T>(url: MaybeRefOrGetter<string>): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>> {
const data = shallowRef<T | undefined>()
const error = shallowRef<Error | undefined>()
const isFinished = ref(false)
fetch(toValue(url))
.then(r => r.json())
.then(r => data.value = r)
.catch(e => error.value = e)
.finally(() => isFinished.value = true)
// 将返回状态存储在变量中
const state: UseFetchReturn<T> = {
data,
error,
isFinished,
}
return {
...state,
// 向对象添加 `then` 允许其被 await。
then(onFulfilled, onRejected) {
return new Promise<UseFetchReturn<T>>((resolve, reject) => {
until(isFinished)
.toBeTruthy()
.then(() => resolve(state))
.then(() => reject(state))
}).then(onFulfilled, onRejected)
},
}
}
无渲染组件
- 使用渲染函数而不是 Vue SFC。
- 将 props 包装在
reactive中,以便轻松地将它们作为 props 传递给 slot。 - 优先使用函数选项作为 prop 类型,而不是自己重新创建它们。
- 仅当函数需要绑定目标时才将 slot 包装在 HTML 元素中。
import type { MouseOptions } from '@vueuse/core'
import { useMouse } from '@vueuse/core'
import { defineComponent, reactive } from 'vue'
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)
}
},
})
有时一个函数可能有多个参数,在这种情况下,您可能需要创建一个新的接口来将所有接口合并为一个组件 props 的单一接口。
import type { TimeAgoOptions } from '@vueuse/core'
import { useTimeAgo } from '@vueuse/core'
interface UseTimeAgoComponentOptions extends Omit<TimeAgoOptions<true>, 'controls'> {
time: MaybeRef<Date | number | string>
}
export const UseTimeAgo = defineComponent<UseTimeAgoComponentOptions>({ /* ... */ })