Guidelines
Here are the guidelines for VueUse functions. You could also take them as a reference for authoring your own composable functions or apps.
You can also find some reasons for those design decisions and also some tips for writing composable functions with Anthony Fu's talk about VueUse:
- Composable Vue - at VueDay 2021
- 可组合的 Vue - at VueConf China 2021 (in Chinese)
General
- Import all Vue APIs from
"vue-demi"
- Use
ref
insteadreactive
whenever possible - Use options object as arguments whenever possible to be more flexible for future extensions.
- Use
shallowRef
instead ofref
when wrapping large amounts of data. - Use
configurableWindow
(etc.) when using global variables likewindow
to be flexible when working with multi-windows, testing mocks, and SSR. - When involved with Web APIs that are not yet implemented by the browser widely, also outputs
isSupported
flag - When using
watch
orwatchEffect
internally, also make theimmediate
andflush
options configurable whenever possible - Use
tryOnUnmounted
to clear the side-effects gracefully - Avoid using console logs
Read also: Best Practice
ShallowRef
Use shallowRef
instead of ref
when wrapping large amounts of data.
export function useFetch<T>(url: MaybeRef<string>) {
// use `shallowRef` to prevent deep reactivity
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)
/* ... */
}
Configurable Globals
When using global variables like window
or document
, support configurableWindow
or configurableDocument
in the options interface to make the function flexible when for scenarios like multi-windows, testing mocks, and SSR.
Learn more about the implementation: _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
// skip when in Node.js environment (SSR)
if (window) {
window.addEventListener('blur', () => {
el = window?.document.activeElement
}, true)
}
/* ... */
}
Usage example:
// in iframe and bind to the parent window
useActiveElement({ window: window.parent })
Watch Options
When using watch
or watchEffect
internally, also make the immediate
and flush
options configurable whenever possible. For example debouncedWatch
import { WatchOptions } from 'vue-demi'
// extend the watch options
export interface DebouncedWatchOptions extends WatchOptions {
debounce?: number
}
export function debouncedWatch(
source: any,
cb: any,
options: DebouncedWatchOptions = {},
): WatchStopHandle {
return watch(
source,
() => /* ... */,
options, // pass watch options
)
}
Controls
We use the controls
option allowing users to use functions with a single return for simple usages, while being able to have more controls and flexibility when needed. Read more: #362.
When to provide a controls
option
- The function is more commonly used with single
ref
or - Examples:
useTimestamp
useInterval
// common usage
const timestamp = useTimestamp()
// more controls for flexibility
const { timestamp, pause, resume } = useTimestamp({ controls: true })
Refer to useTimestamp
s source code for the implementation of proper TypeScript support.
When NOT to provide a controls
option
- The function is more commonly used with multiple returns
- Examples:
useRafFn
useRefHistory
const { pause, resume } = useRafFn(() => {})
isSupported
Flag
When involved with Web APIs that are not yet implemented by the browser widely, also outputs isSupported
flag.
For example 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 Components
- Use render functions instead of Vue SFC
- Wrap the props in
reactive
to easily pass them as props to the slot - Prefer to use the functions options as prop types instead of recreating them yourself
- Only wrap the slot in an HTML element if the function needs a target to bind to
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)
}
},
})
Sometimes a function may have multiple parameters, in that case, you maybe need to create a new interface to merge all the interfaces into a single interface for the component props.
import { useTimeAgo, TimeAgoOptions } from '@vueuse/core'
interface UseTimeAgoComponentOptions extends Omit<TimeAgoOptions<true>, 'controls'> {
time: MaybeRef<Date | number | string>
}
export const UseTimeAgo = defineComponent<UseTimeAgoComponentOptions>({...})