跳至內容

準則

以下是 VueUse 函式的準則。您也可以將它們作為編寫您自己的組合式函式或應用程式的參考。

您還可以找到這些設計決策的一些原因,以及使用 Anthony Fu 關於 VueUse 的演講來編寫組合式函式的一些技巧

通用

  • "vue" 匯入所有 Vue API
  • 盡可能使用 ref 而不是 reactive
  • 盡可能使用選項物件作為參數,以便在未來擴展時更具彈性。
  • 當包裝大量數據時,使用 shallowRef 而不是 ref
  • 當使用像 window 這樣的全域變數時,使用 configurableWindow(等等),以便在使用多個視窗、測試 mock 和 SSR 時具有彈性。
  • 當涉及尚未被瀏覽器廣泛實作的 Web API 時,也輸出 isSupported 標誌
  • 當在內部使用 watchwatchEffect 時,也盡可能使 immediateflush 選項可配置
  • 使用 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)

  /* ... */
}

可配置的全域變數

當使用像 windowdocument 這樣的全域變數時,在選項介面中支援 configurableWindowconfigurableDocument,以便在多個視窗、測試 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 選項

當在內部使用 watchwatchEffect 時,也盡可能使 immediateflush 選項可配置。例如 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 選項

ts
// common usage
const timestamp = useTimestamp()

// more controls for flexibility
const { timestamp, pause, resume } = useTimestamp({ controls: true })

請參考 useTimestamp 的原始碼,了解正確的 TypeScript 支援的實作。

何時提供 controls 選項

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>({ /* ... */ })

在 MIT 許可下發布。