準則
以下是 VueUse 函式的準則。您也可以將它們作為編寫您自己的組合式函式或應用程式的參考。
您還可以找到這些設計決策的一些原因,以及使用 Anthony Fu 關於 VueUse 的演講來編寫組合式函式的一些技巧
- Composable Vue - 在 VueDay 2021
- 可组合的 Vue - 在 VueConf China 2021(中文)
通用
- 從
"vue"
匯入所有 Vue API - 盡可能使用
ref
而不是reactive
- 盡可能使用選項物件作為參數,以便在未來擴展時更具彈性。
- 當包裝大量數據時,使用
shallowRef
而不是ref
。 - 當使用像
window
這樣的全域變數時,使用configurableWindow
(等等),以便在使用多個視窗、測試 mock 和 SSR 時具有彈性。 - 當涉及尚未被瀏覽器廣泛實作的 Web API 時,也輸出
isSupported
標誌 - 當在內部使用
watch
或watchEffect
時,也盡可能使immediate
和flush
選項可配置 - 使用
tryOnUnmounted
來優雅地清除副作用 - 避免使用 console logs
- 當函式是異步時,返回 PromiseLike
另請閱讀:最佳實踐
ShallowRef
當包裝大量數據時,使用 shallowRef
而不是 ref
。
ts
export function useFetch<T>(url: MaybeRefOrGetter<string>) {
// use `shallowRef` to prevent deep reactivity
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
,以便在多個視窗、測試 mock 和 SSR 等情況下使函式具有彈性。
了解更多關於實作的信息:_configurable.ts
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
// skip when in Node.js environment (SSR)
if (window) {
useEventListener(window, 'blur', () => {
el = window?.document.activeElement
}, true)
}
/* ... */
}
使用範例
ts
// in iframe and bind to the parent window
useActiveElement({ window: window.parent })
Watch 選項
當在內部使用 watch
或 watchEffect
時,也盡可能使 immediate
和 flush
選項可配置。例如 watchDebounced
ts
import type { WatchOptions } from 'vue'
// extend the watch options
export interface WatchDebouncedOptions extends WatchOptions {
debounce?: number
}
export function watchDebounced(
source: any,
cb: any,
options: WatchDebouncedOptions = {},
): WatchStopHandle {
return watch(
source,
() => { /* ... */ },
options, // pass watch options
)
}
控制項
我們使用 controls
選項,允許使用者在簡單用法中使用具有單一返回值的函式,同時在需要時能夠有更多的控制和彈性。閱讀更多:#362。
何時提供 controls
選項
- 該函式更常與單個
ref
一起使用,或者 - 範例:
useTimestamp
useInterval
ts
// common usage
const timestamp = useTimestamp()
// more controls for flexibility
const { timestamp, pause, resume } = useTimestamp({ controls: true })
請參考 useTimestamp
的原始碼,了解正確的 TypeScript 支援的實作。
何時不提供 controls
選項
- 該函式更常與多個返回值一起使用
- 範例:
useRafFn
useRefHistory
ts
const { pause, resume } = useRafFn(() => {})
isSupported
標誌
當涉及尚未被瀏覽器廣泛實作的 Web API 時,也輸出 isSupported
標誌。
例如 useShare
ts
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 物件是一個好主意,以便使用者能夠等待該函式。這在 Vue 的 <Suspense>
api 的情況下特別有用。
- 使用
ref
來確定函式何時應該解析,例如isFinished
- 將返回狀態儲存在一個變數中,因為它必須返回兩次,一次在 return 中,一次在 promise 中。
- 返回類型應該是返回類型和 PromiseLike 之間的交集,例如
UseFetchReturn & PromiseLike<UseFetchReturn>
ts
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)
// Store the return state in a variable
const state: UseFetchReturn<T> = {
data,
error,
isFinished,
}
return {
...state,
// Adding `then` to an object allows it to be awaited.
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 元素中
ts
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。
ts
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>({ /* ... */ })