Nuxt/packages/app/src/composables/asyncData.ts

120 lines
2.9 KiB
TypeScript
Raw Normal View History

import { Ref, ref, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue'
import { Nuxt, useNuxt } from 'nuxt/app'
2021-03-17 09:17:18 +00:00
import { ensureReactive, useData } from './data'
export type AsyncDataFn<T> = (ctx?: Nuxt) => Promise<T>
export interface AsyncDataOptions {
server?: boolean
defer?: boolean
}
export interface AsyncDataObj<T> {
data: Ref<T>
pending: Ref<boolean>
2021-03-17 09:17:18 +00:00
refresh: () => Promise<void>
error?: any
}
export function useAsyncData (defaults?: AsyncDataOptions) {
const nuxt = useNuxt()
const vm = getCurrentInstance()
2021-03-17 09:17:18 +00:00
const data = useData(nuxt, vm)
let dataRef = 1
2021-03-17 09:17:18 +00:00
const onMountedCbs: Array<() => void> = []
if (process.client) {
onMounted(() => {
onMountedCbs.forEach((cb) => { cb() })
onMountedCbs.splice(0, onMountedCbs.length)
})
2021-03-17 09:17:18 +00:00
onUnmounted(() => onMountedCbs.splice(0, onMountedCbs.length))
}
2021-03-17 09:17:18 +00:00
return async function asyncData<T = Record<string, any>> (
handler: AsyncDataFn<T>,
options?: AsyncDataOptions
): Promise<AsyncDataObj<T>> {
if (typeof handler !== 'function') {
throw new TypeError('asyncData handler must be a function')
}
options = {
server: true,
defer: false,
...defaults,
...options
}
const key = String(dataRef++)
const pending = ref(true)
2021-03-17 09:17:18 +00:00
const datastore = ensureReactive(data, key)
const fetch = async () => {
pending.value = true
const _handler = handler(nuxt)
if (_handler instanceof Promise) {
// Let user resolve if request is promise
// TODO: handle error
const result = await _handler
2021-03-17 09:17:18 +00:00
for (const _key in result) {
datastore[_key] = result[_key]
}
2021-03-17 09:17:18 +00:00
pending.value = false
} else {
// Invalid request
throw new TypeError('Invalid asyncData handler: ' + _handler)
}
}
const clientOnly = options.server === false
// Client side
if (process.client) {
// 1. Hydration (server: true): no fetch
if (nuxt.isHydrating && options.server) {
pending.value = false
}
// 2. Initial load (server: false): fetch on mounted
if (nuxt.isHydrating && !options.server) {
// Fetch on mounted (initial load or deferred fetch)
onMountedCbs.push(fetch)
} else if (!nuxt.isHydrating) {
if (options.defer) {
// 3. Navigation (defer: true): fetch on mounted
onMountedCbs.push(fetch)
} else {
// 4. Navigation (defer: false): await fetch
await fetch()
}
}
// Watch handler
watch(handler.bind(null, nuxt), fetch)
}
// Server side
if (process.server && !clientOnly) {
await fetch()
}
return {
2021-03-17 09:17:18 +00:00
data: datastore,
pending,
refresh: fetch
}
}
}
2021-03-17 09:17:18 +00:00
export function asyncData<T = Record<string, any>> (
handler: AsyncDataFn<T>,
options?: AsyncDataOptions
): Promise<AsyncDataObj<T>> {
return useAsyncData()(handler, options)
}