diff --git a/packages/nuxt3/src/app/composables/asyncData.ts b/packages/nuxt3/src/app/composables/asyncData.ts index d25d877b6a..db6f5455de 100644 --- a/packages/nuxt3/src/app/composables/asyncData.ts +++ b/packages/nuxt3/src/app/composables/asyncData.ts @@ -1,6 +1,7 @@ import { Ref, ref, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue' import { Nuxt, useNuxt } from 'nuxt/app' -import { useData } from './data' + +import { ensureReactive, useData } from './data' export type AsyncDataFn = (ctx?: Nuxt) => Promise @@ -12,7 +13,7 @@ export interface AsyncDataOptions { export interface AsyncDataObj { data: Ref pending: Ref - refresh: Function + refresh: () => Promise error?: any } @@ -20,10 +21,10 @@ export function useAsyncData (defaults?: AsyncDataOptions) { const nuxt = useNuxt() const vm = getCurrentInstance() - let data = useData(nuxt, vm) + const data = useData(nuxt, vm) let dataRef = 1 - const onMountedCbs = [] + const onMountedCbs: Array<() => void> = [] if (process.client) { onMounted(() => { @@ -31,13 +32,13 @@ export function useAsyncData (defaults?: AsyncDataOptions) { onMountedCbs.splice(0, onMountedCbs.length) }) - onUnmounted(() => { - onMountedCbs.splice(0, onMountedCbs.length) - data = null - }) + onUnmounted(() => onMountedCbs.splice(0, onMountedCbs.length)) } - return async function asyncData (handler: AsyncDataFn, options?: AsyncDataOptions): Promise> { + return async function asyncData> ( + handler: AsyncDataFn, + options?: AsyncDataOptions + ): Promise> { if (typeof handler !== 'function') { throw new TypeError('asyncData handler must be a function') } @@ -51,6 +52,8 @@ export function useAsyncData (defaults?: AsyncDataOptions) { const key = String(dataRef++) const pending = ref(true) + const datastore = ensureReactive(data, key) + const fetch = async () => { pending.value = true const _handler = handler(nuxt) @@ -59,11 +62,11 @@ export function useAsyncData (defaults?: AsyncDataOptions) { // Let user resolve if request is promise // TODO: handle error const result = await _handler - if (!data[key]) { - data[key] = result - } else { - Object.assign(data[key], result) + + for (const _key in result) { + datastore[_key] = result[_key] } + pending.value = false } else { // Invalid request @@ -81,14 +84,10 @@ export function useAsyncData (defaults?: AsyncDataOptions) { } // 2. Initial load (server: false): fetch on mounted if (nuxt.isHydrating && !options.server) { - // Force tracking it - data[key] = {} // Fetch on mounted (initial load or deferred fetch) onMountedCbs.push(fetch) } else if (!nuxt.isHydrating) { if (options.defer) { - // Force tracking it - data[key] = {} // 3. Navigation (defer: true): fetch on mounted onMountedCbs.push(fetch) } else { @@ -105,13 +104,16 @@ export function useAsyncData (defaults?: AsyncDataOptions) { await fetch() } return { - data: data[key], + data: datastore, pending, refresh: fetch } } } -export function asyncData (handler: AsyncDataFn, options?: AsyncDataOptions): Promise> { +export function asyncData> ( + handler: AsyncDataFn, + options?: AsyncDataOptions +): Promise> { return useAsyncData()(handler, options) } diff --git a/packages/nuxt3/src/app/composables/data.ts b/packages/nuxt3/src/app/composables/data.ts index 5144edad73..0693eb83f5 100644 --- a/packages/nuxt3/src/app/composables/data.ts +++ b/packages/nuxt3/src/app/composables/data.ts @@ -1,12 +1,28 @@ -import { getCurrentInstance, reactive, isReactive } from 'vue' +import { getCurrentInstance, isReactive, reactive, UnwrapRef } from 'vue' + import { useNuxt } from 'nuxt/app' +export function ensureReactive< + T extends Record, + K extends keyof T +> (data: T, key: K): UnwrapRef { + if (!isReactive(data[key])) { + data[key] = reactive(data[key] || ({} as T[K])) + } + return data[key] +} + /** * Returns a unique string suitable for syncing data between server and client. * @param nuxt (optional) A Nuxt instance * @param vm (optional) A Vue component - by default it will use the current instance */ export function useSSRRef (nuxt = useNuxt(), vm = getCurrentInstance()): string { + if (!vm) { + throw new Error('This must be called within a setup function.') + } + + // Server if (process.server) { if (!vm.attrs['data-ssr-ref']) { nuxt._refCtr = nuxt._refCtr || 1 @@ -15,9 +31,9 @@ export function useSSRRef (nuxt = useNuxt(), vm = getCurrentInstance()): string return vm.attrs['data-ssr-ref'] as string } - if (process.client) { - return vm.vnode.el?.dataset?.ssrRef || String(Math.random()) /* TODO: unique value for multiple calls */ - } + // Client + /* TODO: unique value for multiple calls */ + return vm.vnode.el?.dataset?.ssrRef || String(Math.random()) } /** @@ -25,14 +41,13 @@ export function useSSRRef (nuxt = useNuxt(), vm = getCurrentInstance()): string * @param nuxt (optional) A Nuxt instance * @param vm (optional) A Vue component - by default it will use the current instance */ -export function useData (nuxt = useNuxt(), vm = getCurrentInstance()): ReturnType { +export function useData> ( + nuxt = useNuxt(), + vm = getCurrentInstance() +): UnwrapRef { const ssrRef = useSSRRef(nuxt, vm) nuxt.payload.data = nuxt.payload.data || {} - if (!isReactive(nuxt.payload.data[ssrRef])) { - nuxt.payload.data[ssrRef] = reactive(nuxt.payload.data[ssrRef] || {}) - } - - return nuxt.payload.data[ssrRef] + return ensureReactive(nuxt.payload.data, ssrRef) } diff --git a/packages/nuxt3/src/app/nuxt/composables.ts b/packages/nuxt3/src/app/nuxt/composables.ts index 9cdca9ae4b..e61d1f2de0 100644 --- a/packages/nuxt3/src/app/nuxt/composables.ts +++ b/packages/nuxt3/src/app/nuxt/composables.ts @@ -1,9 +1,9 @@ import { getCurrentInstance } from 'vue' import type { Nuxt } from 'nuxt/app' -let currentNuxtInstance: Nuxt +let currentNuxtInstance: Nuxt | null -export const setNuxtInstance = (nuxt: Nuxt) => { +export const setNuxtInstance = (nuxt: Nuxt | null) => { currentNuxtInstance = nuxt } @@ -15,21 +15,20 @@ export const setNuxtInstance = (nuxt: Nuxt) => { export async function callWithNuxt (nuxt: Nuxt, setup: () => any) { setNuxtInstance(nuxt) const p = setup() - setNuxtInstance(undefined) + setNuxtInstance(null) await p } /** * Returns the current Nuxt instance. */ -export function useNuxt () { +export function useNuxt (): Nuxt { const vm = getCurrentInstance() - if (!vm && !currentNuxtInstance) { - throw new Error('nuxt instance unavailable') - } - if (!vm) { + if (!currentNuxtInstance) { + throw new Error('nuxt instance unavailable') + } return currentNuxtInstance } diff --git a/packages/nuxt3/src/app/nuxt/index.ts b/packages/nuxt3/src/app/nuxt/index.ts index 95ec40ed0c..f6ce4a9ea5 100644 --- a/packages/nuxt3/src/app/nuxt/index.ts +++ b/packages/nuxt3/src/app/nuxt/index.ts @@ -15,8 +15,8 @@ export interface Nuxt { ssrContext?: Record payload: { - serverRendered?: true, - data?: object + serverRendered?: true + data?: Record rendered?: Function [key: string]: any } @@ -36,7 +36,6 @@ export interface CreateOptions { export function createNuxt (options: CreateOptions) { const nuxt: Nuxt = { - app: undefined, provide: undefined, globalName: 'nuxt', state: {}, @@ -69,6 +68,8 @@ export function createNuxt (options: CreateOptions) { serverRendered: true // TODO: legacy } + nuxt.ssrContext = nuxt.ssrContext || {} + // Expose to server renderer to create window.__NUXT__ nuxt.ssrContext.payload = nuxt.payload }