mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(app): improve composables (#183)
This commit is contained in:
parent
960523c4b8
commit
451fc29b60
@ -1,6 +1,7 @@
|
|||||||
import { Ref, ref, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue'
|
import { Ref, ref, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue'
|
||||||
import { Nuxt, useNuxt } from 'nuxt/app'
|
import { Nuxt, useNuxt } from 'nuxt/app'
|
||||||
import { useData } from './data'
|
|
||||||
|
import { ensureReactive, useData } from './data'
|
||||||
|
|
||||||
export type AsyncDataFn<T> = (ctx?: Nuxt) => Promise<T>
|
export type AsyncDataFn<T> = (ctx?: Nuxt) => Promise<T>
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ export interface AsyncDataOptions {
|
|||||||
export interface AsyncDataObj<T> {
|
export interface AsyncDataObj<T> {
|
||||||
data: Ref<T>
|
data: Ref<T>
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
refresh: Function
|
refresh: () => Promise<void>
|
||||||
error?: any
|
error?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,10 +21,10 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
let data = useData(nuxt, vm)
|
const data = useData(nuxt, vm)
|
||||||
let dataRef = 1
|
let dataRef = 1
|
||||||
|
|
||||||
const onMountedCbs = []
|
const onMountedCbs: Array<() => void> = []
|
||||||
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -31,13 +32,13 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
onMountedCbs.splice(0, onMountedCbs.length)
|
onMountedCbs.splice(0, onMountedCbs.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => onMountedCbs.splice(0, onMountedCbs.length))
|
||||||
onMountedCbs.splice(0, onMountedCbs.length)
|
|
||||||
data = null
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return async function asyncData<T = any> (handler: AsyncDataFn<T>, options?: AsyncDataOptions): Promise<AsyncDataObj<T>> {
|
return async function asyncData<T = Record<string, any>> (
|
||||||
|
handler: AsyncDataFn<T>,
|
||||||
|
options?: AsyncDataOptions
|
||||||
|
): Promise<AsyncDataObj<T>> {
|
||||||
if (typeof handler !== 'function') {
|
if (typeof handler !== 'function') {
|
||||||
throw new TypeError('asyncData handler must be a function')
|
throw new TypeError('asyncData handler must be a function')
|
||||||
}
|
}
|
||||||
@ -51,6 +52,8 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
const key = String(dataRef++)
|
const key = String(dataRef++)
|
||||||
const pending = ref(true)
|
const pending = ref(true)
|
||||||
|
|
||||||
|
const datastore = ensureReactive(data, key)
|
||||||
|
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
pending.value = true
|
pending.value = true
|
||||||
const _handler = handler(nuxt)
|
const _handler = handler(nuxt)
|
||||||
@ -59,11 +62,11 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
// Let user resolve if request is promise
|
// Let user resolve if request is promise
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
const result = await _handler
|
const result = await _handler
|
||||||
if (!data[key]) {
|
|
||||||
data[key] = result
|
for (const _key in result) {
|
||||||
} else {
|
datastore[_key] = result[_key]
|
||||||
Object.assign(data[key], result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pending.value = false
|
pending.value = false
|
||||||
} else {
|
} else {
|
||||||
// Invalid request
|
// Invalid request
|
||||||
@ -81,14 +84,10 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
}
|
}
|
||||||
// 2. Initial load (server: false): fetch on mounted
|
// 2. Initial load (server: false): fetch on mounted
|
||||||
if (nuxt.isHydrating && !options.server) {
|
if (nuxt.isHydrating && !options.server) {
|
||||||
// Force tracking it
|
|
||||||
data[key] = {}
|
|
||||||
// Fetch on mounted (initial load or deferred fetch)
|
// Fetch on mounted (initial load or deferred fetch)
|
||||||
onMountedCbs.push(fetch)
|
onMountedCbs.push(fetch)
|
||||||
} else if (!nuxt.isHydrating) {
|
} else if (!nuxt.isHydrating) {
|
||||||
if (options.defer) {
|
if (options.defer) {
|
||||||
// Force tracking it
|
|
||||||
data[key] = {}
|
|
||||||
// 3. Navigation (defer: true): fetch on mounted
|
// 3. Navigation (defer: true): fetch on mounted
|
||||||
onMountedCbs.push(fetch)
|
onMountedCbs.push(fetch)
|
||||||
} else {
|
} else {
|
||||||
@ -105,13 +104,16 @@ export function useAsyncData (defaults?: AsyncDataOptions) {
|
|||||||
await fetch()
|
await fetch()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
data: data[key],
|
data: datastore,
|
||||||
pending,
|
pending,
|
||||||
refresh: fetch
|
refresh: fetch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function asyncData<T = any> (handler: AsyncDataFn<T>, options?: AsyncDataOptions): Promise<AsyncDataObj<T>> {
|
export function asyncData<T = Record<string, any>> (
|
||||||
|
handler: AsyncDataFn<T>,
|
||||||
|
options?: AsyncDataOptions
|
||||||
|
): Promise<AsyncDataObj<T>> {
|
||||||
return useAsyncData()(handler, options)
|
return useAsyncData()(handler, options)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
import { getCurrentInstance, reactive, isReactive } from 'vue'
|
import { getCurrentInstance, isReactive, reactive, UnwrapRef } from 'vue'
|
||||||
|
|
||||||
import { useNuxt } from 'nuxt/app'
|
import { useNuxt } from 'nuxt/app'
|
||||||
|
|
||||||
|
export function ensureReactive<
|
||||||
|
T extends Record<string, any>,
|
||||||
|
K extends keyof T
|
||||||
|
> (data: T, key: K): UnwrapRef<T[K]> {
|
||||||
|
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.
|
* Returns a unique string suitable for syncing data between server and client.
|
||||||
* @param nuxt (optional) A Nuxt instance
|
* @param nuxt (optional) A Nuxt instance
|
||||||
* @param vm (optional) A Vue component - by default it will use the current instance
|
* @param vm (optional) A Vue component - by default it will use the current instance
|
||||||
*/
|
*/
|
||||||
export function useSSRRef (nuxt = useNuxt(), vm = getCurrentInstance()): string {
|
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 (process.server) {
|
||||||
if (!vm.attrs['data-ssr-ref']) {
|
if (!vm.attrs['data-ssr-ref']) {
|
||||||
nuxt._refCtr = nuxt._refCtr || 1
|
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
|
return vm.attrs['data-ssr-ref'] as string
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.client) {
|
// Client
|
||||||
return vm.vnode.el?.dataset?.ssrRef || String(Math.random()) /* TODO: unique value for multiple calls */
|
/* 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 nuxt (optional) A Nuxt instance
|
||||||
* @param vm (optional) A Vue component - by default it will use the current instance
|
* @param vm (optional) A Vue component - by default it will use the current instance
|
||||||
*/
|
*/
|
||||||
export function useData (nuxt = useNuxt(), vm = getCurrentInstance()): ReturnType<typeof reactive> {
|
export function useData<T = Record<string, any>> (
|
||||||
|
nuxt = useNuxt(),
|
||||||
|
vm = getCurrentInstance()
|
||||||
|
): UnwrapRef<T> {
|
||||||
const ssrRef = useSSRRef(nuxt, vm)
|
const ssrRef = useSSRRef(nuxt, vm)
|
||||||
|
|
||||||
nuxt.payload.data = nuxt.payload.data || {}
|
nuxt.payload.data = nuxt.payload.data || {}
|
||||||
|
|
||||||
if (!isReactive(nuxt.payload.data[ssrRef])) {
|
return ensureReactive(nuxt.payload.data, ssrRef)
|
||||||
nuxt.payload.data[ssrRef] = reactive(nuxt.payload.data[ssrRef] || {})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nuxt.payload.data[ssrRef]
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance } from 'vue'
|
||||||
import type { Nuxt } from 'nuxt/app'
|
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
|
currentNuxtInstance = nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,21 +15,20 @@ export const setNuxtInstance = (nuxt: Nuxt) => {
|
|||||||
export async function callWithNuxt (nuxt: Nuxt, setup: () => any) {
|
export async function callWithNuxt (nuxt: Nuxt, setup: () => any) {
|
||||||
setNuxtInstance(nuxt)
|
setNuxtInstance(nuxt)
|
||||||
const p = setup()
|
const p = setup()
|
||||||
setNuxtInstance(undefined)
|
setNuxtInstance(null)
|
||||||
await p
|
await p
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current Nuxt instance.
|
* Returns the current Nuxt instance.
|
||||||
*/
|
*/
|
||||||
export function useNuxt () {
|
export function useNuxt (): Nuxt {
|
||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
if (!vm && !currentNuxtInstance) {
|
|
||||||
throw new Error('nuxt instance unavailable')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
|
if (!currentNuxtInstance) {
|
||||||
|
throw new Error('nuxt instance unavailable')
|
||||||
|
}
|
||||||
return currentNuxtInstance
|
return currentNuxtInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ export interface Nuxt {
|
|||||||
|
|
||||||
ssrContext?: Record<string, any>
|
ssrContext?: Record<string, any>
|
||||||
payload: {
|
payload: {
|
||||||
serverRendered?: true,
|
serverRendered?: true
|
||||||
data?: object
|
data?: Record<string, any>
|
||||||
rendered?: Function
|
rendered?: Function
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
@ -36,7 +36,6 @@ export interface CreateOptions {
|
|||||||
|
|
||||||
export function createNuxt (options: CreateOptions) {
|
export function createNuxt (options: CreateOptions) {
|
||||||
const nuxt: Nuxt = {
|
const nuxt: Nuxt = {
|
||||||
app: undefined,
|
|
||||||
provide: undefined,
|
provide: undefined,
|
||||||
globalName: 'nuxt',
|
globalName: 'nuxt',
|
||||||
state: {},
|
state: {},
|
||||||
@ -69,6 +68,8 @@ export function createNuxt (options: CreateOptions) {
|
|||||||
serverRendered: true // TODO: legacy
|
serverRendered: true // TODO: legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nuxt.ssrContext = nuxt.ssrContext || {}
|
||||||
|
|
||||||
// Expose to server renderer to create window.__NUXT__
|
// Expose to server renderer to create window.__NUXT__
|
||||||
nuxt.ssrContext.payload = nuxt.payload
|
nuxt.ssrContext.payload = nuxt.payload
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user