mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(nuxt3, bridge): add lazy helpers (useLazyAsyncData
and useLazyFetch
) (#1861)
This commit is contained in:
parent
3e1239d8c7
commit
f011a60dae
@ -186,9 +186,42 @@ const wrapProperty = (property, makeComputed = true) => () => {
|
||||
|
||||
### `useAsync` and `useFetch`
|
||||
|
||||
There is currently no replacement for these two functions with bridge.
|
||||
These two composables can be replaced with `useLazyAsyncData` and `useLazyFetch`, which are documented [in the Nuxt 3 docs](/docs/usage/data-fetching). Just like the previous `@nuxtjs/composition-api` composables, these composables do not block route navigation on the client-side (hence the 'lazy' part of the name).
|
||||
|
||||
You can continue to use `useAsync` and `useFetch` by importing them from `@nuxtjs/composition-api`, which is shimmed by Nuxt Bridge.
|
||||
::alert
|
||||
Note that the API is entirely different, despite similar sounding names. Importantly, you should not attempt to change the value of other variables outside the composable (as you may have been doing with the previous `useFetch`).
|
||||
::
|
||||
|
||||
Migrating to the new composables from `useAsync`:
|
||||
|
||||
```diff
|
||||
<script setup>
|
||||
- import { useAsync } from '@nuxtjs/composition-api'
|
||||
+ import { useLazyAsyncData, useLazyFetch } from '#app'
|
||||
- const posts = useAsync(() => $fetch('/api/posts'))
|
||||
+ const { data: posts } = useLazyAsyncData('posts', () => $fetch('/api/posts'))
|
||||
+ // or, more simply!
|
||||
+ const { data: posts } = useLazyFetch('/api/posts')
|
||||
</script>
|
||||
```
|
||||
|
||||
Migrating to the new composables from `useFetch`:
|
||||
|
||||
```diff
|
||||
<script setup>
|
||||
- import { useFetch } from '@nuxtjs/composition-api'
|
||||
+ import { useLazyAsyncData, useLazyFetch } from '#app'
|
||||
- const posts = ref([])
|
||||
- const { fetch } = useFetch(() => { posts.value = await $fetch('/api/posts') })
|
||||
+ const { data: posts, refresh } = useLazyAsyncData('posts', () => $fetch('/api/posts'))
|
||||
+ // or, more simply!
|
||||
+ const { data: posts, refresh } = useLazyFetch('/api/posts')
|
||||
function updatePosts() {
|
||||
- return fetch()
|
||||
+ return refresh()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `useMeta`
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Data Fetching
|
||||
|
||||
Nuxt provides `useFetch` and `useAsyncData` to handle data fetching within your application.
|
||||
Nuxt provides `useFetch`, `useLazyFetch`, `useAsyncData` and `useLazyAsyncData` to handle data fetching within your application.
|
||||
|
||||
## `useAsyncData`
|
||||
|
||||
@ -12,19 +12,20 @@ Within your pages, components and plugins you can use `useAsyncData` to get acce
|
||||
useAsyncData(
|
||||
key: string,
|
||||
fn: () => Object,
|
||||
options?: { defer: boolean, server: boolean }
|
||||
options?: { lazy: boolean, server: boolean }
|
||||
)
|
||||
```
|
||||
|
||||
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests
|
||||
* **fn** an asynchronous function that returns a value.
|
||||
* **options**:
|
||||
* _defer_: whether to load the route before resolving the async function (defaults to `false`)
|
||||
* _server_: whether the fetch the data on server-side (defaults to `true`)
|
||||
* _lazy_: whether to resolve the async function after loading the route, instead of blocking navigation (defaults to `false`)
|
||||
* _default_: a factory function to set the default value of the data, before the async function resolves - particularly useful with the `lazy: true` option
|
||||
* _server_: whether to fetch the data on server-side (defaults to `true`)
|
||||
* _transform_: A function that can be used to alter fn result after resolving
|
||||
* _pick_: Only pick specified keys in this array from fn result
|
||||
|
||||
Under the hood, `defer: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `defer: true` and implementing a loading state instead for a snappier user experience.
|
||||
Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience.
|
||||
|
||||
### Example
|
||||
|
||||
@ -46,6 +47,10 @@ const { data } = await useAsyncData('count', () => $fetch('/api/count'))
|
||||
</template>
|
||||
```
|
||||
|
||||
## `useLazyAsyncData`
|
||||
|
||||
This composable behaves identically to `useAsyncData` with the `lazy: true` option set. In other words, the async function does not block navigation. That means you will need to handle the situation where the data is `null` (or whatever value you have provided in a custom `default` factory function).
|
||||
|
||||
## `useFetch`
|
||||
|
||||
Within your pages, components and plugins you can use `useFetch` to get universally fetch from any URL.
|
||||
@ -70,8 +75,9 @@ Available options:
|
||||
* `params`: Query params
|
||||
* `baseURL`: Base URL for request
|
||||
* Options from `useAsyncData`
|
||||
* `defer`
|
||||
* `lazy`
|
||||
* `server`
|
||||
* `default`
|
||||
* `pick`
|
||||
* `transform`
|
||||
|
||||
@ -87,9 +93,13 @@ const { data } = await useFetch('/api/count')
|
||||
</template>
|
||||
```
|
||||
|
||||
### Best practices
|
||||
## `useLazyFetch`
|
||||
|
||||
The data returned by `useAsyncData` will be stored inside the page payload. This means that every key returned that is not used in your component will be added to the payload.
|
||||
This composable behaves identically to `useFetch` with the `lazy: true` option set. In other words, the async function does not block navigation. That means you will need to handle the situation where the data is `null` (or whatever value you have provided in a custom `default` factory function).
|
||||
|
||||
## Best practices
|
||||
|
||||
The data returned by these composables will be stored inside the page payload. This means that every key returned that is not used in your component will be added to the payload.
|
||||
|
||||
::alert{icon=👉}
|
||||
**We strongly recommend you only select the keys that you will use in your component.**
|
||||
@ -124,7 +134,7 @@ const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['ti
|
||||
</template>
|
||||
```
|
||||
|
||||
### Using async setup
|
||||
## Using async setup
|
||||
|
||||
If you are using `async setup()`, the current component instance will be lost after the first `await`. (This is a Vue 3 limitation.) If you want to use multiple async operations, such as multiple calls to `useFetch`, you will need to use `<script setup>` or await them together at the end of setup.
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
"hash-sum": "^2.0.0",
|
||||
"magic-string": "^0.25.7",
|
||||
"mlly": "^0.3.13",
|
||||
"murmurhash-es": "^0.1.1",
|
||||
"node-fetch": "^3.1.0",
|
||||
"nuxi": "3.0.0",
|
||||
"p-debounce": "^4.0.0",
|
||||
|
@ -2,6 +2,22 @@ import Vue from 'vue'
|
||||
import { createHooks } from 'hookable'
|
||||
import { setNuxtAppInstance } from '#app'
|
||||
|
||||
// Reshape payload to match key `useLazyAsyncData` expects
|
||||
function proxiedState (state) {
|
||||
state._asyncData = state._asyncData || {}
|
||||
return new Proxy(state, {
|
||||
get (target, prop) {
|
||||
if (prop === 'data') {
|
||||
return target._asyncData
|
||||
}
|
||||
if (prop === '_data') {
|
||||
return target.state
|
||||
}
|
||||
return Reflect.get(target, prop)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default (ctx, inject) => {
|
||||
const nuxtApp = {
|
||||
vueApp: {
|
||||
@ -21,7 +37,8 @@ export default (ctx, inject) => {
|
||||
},
|
||||
provide: inject,
|
||||
globalName: 'nuxt',
|
||||
payload: process.client ? ctx.nuxtState : ctx.ssrContext.nuxt,
|
||||
payload: proxiedState(process.client ? ctx.nuxtState : ctx.ssrContext.nuxt),
|
||||
_asyncDataPromises: [],
|
||||
isHydrating: ctx.isHMR,
|
||||
nuxt2Context: ctx
|
||||
}
|
||||
|
1
packages/bridge/src/runtime/asyncData.ts
Symbolic link
1
packages/bridge/src/runtime/asyncData.ts
Symbolic link
@ -0,0 +1 @@
|
||||
../../../nuxt3/src/app/composables/asyncData.ts
|
@ -6,11 +6,15 @@ import type { Route } from 'vue-router'
|
||||
import type { RuntimeConfig } from '@nuxt/kit'
|
||||
import { useNuxtApp } from './app'
|
||||
|
||||
export { useLazyAsyncData } from './asyncData'
|
||||
export { useLazyFetch } from './fetch'
|
||||
|
||||
export * from '@vue/composition-api'
|
||||
|
||||
const mock = () => () => { throw new Error('not implemented') }
|
||||
|
||||
export const useAsyncData = mock()
|
||||
export const useFetch = mock()
|
||||
export const useHydration = mock()
|
||||
|
||||
// Runtime config helper
|
||||
|
1
packages/bridge/src/runtime/fetch.ts
Symbolic link
1
packages/bridge/src/runtime/fetch.ts
Symbolic link
@ -0,0 +1 @@
|
||||
../../../nuxt3/src/app/composables/fetch.ts
|
7
packages/bridge/src/runtime/vue2-bridge.d.ts
vendored
Normal file
7
packages/bridge/src/runtime/vue2-bridge.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
export * from '@vue/composition-api'
|
||||
|
||||
export declare const isFunction: (fn: unknown) => boolean
|
||||
|
||||
export { Vue as default }
|
@ -1,8 +1,8 @@
|
||||
import { onBeforeMount, onUnmounted, ref, getCurrentInstance } from 'vue'
|
||||
import { onBeforeMount, onServerPrefetch, onUnmounted, ref, getCurrentInstance } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { NuxtApp, useNuxtApp } from '#app'
|
||||
|
||||
export type _Transform<Input=any, Output=any> = (input: Input) => Output
|
||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output
|
||||
|
||||
export type PickFrom<T, K extends Array<string>> = T extends Array<any> ? T : T extends Record<string, any> ? Pick<T, K[number]> : T
|
||||
export type KeysOf<T> = Array<keyof T extends string ? keyof T : string>
|
||||
@ -14,7 +14,7 @@ export interface AsyncDataOptions<
|
||||
PickKeys extends KeyOfRes<_Transform> = KeyOfRes<Transform>
|
||||
> {
|
||||
server?: boolean
|
||||
defer?: boolean
|
||||
lazy?: boolean
|
||||
default?: () => DataT
|
||||
transform?: Transform
|
||||
pick?: PickKeys
|
||||
@ -39,7 +39,7 @@ export function useAsyncData<
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options: AsyncDataOptions<DataT, Transform, PickKeys> = {}
|
||||
) : AsyncData<PickFrom<ReturnType<Transform>, PickKeys>> {
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>> {
|
||||
// Validate arguments
|
||||
if (typeof key !== 'string') {
|
||||
throw new TypeError('asyncData key must be a string')
|
||||
@ -49,7 +49,12 @@ export function useAsyncData<
|
||||
}
|
||||
|
||||
// Apply defaults
|
||||
options = { server: true, defer: false, default: getDefault, ...options }
|
||||
options = { server: true, default: getDefault, ...options }
|
||||
// TODO: remove support for `defer` in Nuxt 3 RC
|
||||
if ((options as any).defer) {
|
||||
console.warn('[useAsyncData] `defer` has been renamed to `lazy`. Support for `defer` will be removed in RC.')
|
||||
}
|
||||
options.lazy = options.lazy ?? (options as any).defer ?? false
|
||||
|
||||
// Setup nuxt instance payload
|
||||
const nuxt = useNuxtApp()
|
||||
@ -109,7 +114,8 @@ export function useAsyncData<
|
||||
|
||||
// Server side
|
||||
if (process.server && fetchOnServer) {
|
||||
asyncData.refresh()
|
||||
const promise = asyncData.refresh()
|
||||
onServerPrefetch(() => promise)
|
||||
}
|
||||
|
||||
// Client side
|
||||
@ -120,14 +126,14 @@ export function useAsyncData<
|
||||
}
|
||||
// 2. Initial load (server: false): fetch on mounted
|
||||
if (nuxt.isHydrating && clientOnly) {
|
||||
// Fetch on mounted (initial load or deferred fetch)
|
||||
// Fetch on mounted (initial load or lazy fetch)
|
||||
instance._nuxtOnBeforeMountCbs.push(asyncData.refresh)
|
||||
} else if (!nuxt.isHydrating) { // Navigation
|
||||
if (options.defer) {
|
||||
// 3. Navigation (defer: true): fetch on mounted
|
||||
if (options.lazy) {
|
||||
// 3. Navigation (lazy: true): fetch on mounted
|
||||
instance._nuxtOnBeforeMountCbs.push(asyncData.refresh)
|
||||
} else {
|
||||
// 4. Navigation (defer: false): await fetch
|
||||
// 4. Navigation (lazy: false): await fetch
|
||||
asyncData.refresh()
|
||||
}
|
||||
}
|
||||
@ -141,6 +147,18 @@ export function useAsyncData<
|
||||
return asyncDataPromise as AsyncData<DataT>
|
||||
}
|
||||
|
||||
export function useLazyAsyncData<
|
||||
DataT,
|
||||
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options: Omit<AsyncDataOptions<DataT, Transform, PickKeys>, 'lazy'> = {}
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>> {
|
||||
return useAsyncData(key, handler, { ...options, lazy: true })
|
||||
}
|
||||
|
||||
function pick (obj: Record<string, any>, keys: string[]) {
|
||||
const newObj = {}
|
||||
for (const key of keys) {
|
||||
|
@ -13,11 +13,11 @@ export type UseFetchOptions<
|
||||
> = AsyncDataOptions<DataT, Transform, PickKeys> & FetchOptions & { key?: string }
|
||||
|
||||
export function useFetch<
|
||||
ReqT extends string = string,
|
||||
ResT = FetchResult<ReqT>,
|
||||
Transform extends (res: ResT) => any = (res: ResT) => ResT,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
ReqT extends string = string,
|
||||
ResT = FetchResult<ReqT>,
|
||||
Transform extends (res: ResT) => any = (res: ResT) => ResT,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
url: ReqT,
|
||||
opts: UseFetchOptions<ResT, Transform, PickKeys> = {}
|
||||
) {
|
||||
@ -38,6 +38,18 @@ export function useFetch<
|
||||
return useAsyncData(opts.key, () => $fetch(url, opts) as Promise<ResT>, opts)
|
||||
}
|
||||
|
||||
export function useLazyFetch<
|
||||
ReqT extends string = string,
|
||||
ResT = FetchResult<ReqT>,
|
||||
Transform extends (res: ResT) => any = (res: ResT) => ResT,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
url: ReqT,
|
||||
opts: Omit<UseFetchOptions<ResT, Transform, PickKeys>, 'lazy'> = {}
|
||||
) {
|
||||
return useFetch(url, { ...opts, lazy: true })
|
||||
}
|
||||
|
||||
function generateKey (keys) {
|
||||
return '$f' + murmurHashV3(JSON.stringify(keys))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export { defineNuxtComponent } from './component'
|
||||
export { useAsyncData } from './asyncData'
|
||||
export { useAsyncData, useLazyAsyncData } from './asyncData'
|
||||
export { useHydration } from './hydrate'
|
||||
export { useState } from './state'
|
||||
export { useFetch } from './fetch'
|
||||
export { useFetch, useLazyFetch } from './fetch'
|
||||
|
@ -6,12 +6,14 @@ export const Nuxt3AutoImports: AutoImportSource[] = [
|
||||
from: '#app',
|
||||
names: [
|
||||
'useAsyncData',
|
||||
'useLazyAsyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig',
|
||||
'useState',
|
||||
'useFetch'
|
||||
'useFetch',
|
||||
'useLazyFetch'
|
||||
]
|
||||
},
|
||||
// #meta
|
||||
|
Loading…
Reference in New Issue
Block a user