mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-13 09:33:54 +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`
|
### `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`
|
### `useMeta`
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Data Fetching
|
# 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`
|
## `useAsyncData`
|
||||||
|
|
||||||
@ -12,19 +12,20 @@ Within your pages, components and plugins you can use `useAsyncData` to get acce
|
|||||||
useAsyncData(
|
useAsyncData(
|
||||||
key: string,
|
key: string,
|
||||||
fn: () => Object,
|
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
|
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests
|
||||||
* **fn** an asynchronous function that returns a value.
|
* **fn** an asynchronous function that returns a value.
|
||||||
* **options**:
|
* **options**:
|
||||||
* _defer_: whether to load the route before resolving the async function (defaults to `false`)
|
* _lazy_: whether to resolve the async function after loading the route, instead of blocking navigation (defaults to `false`)
|
||||||
* _server_: whether the fetch the data on server-side (defaults to `true`)
|
* _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
|
* _transform_: A function that can be used to alter fn result after resolving
|
||||||
* _pick_: Only pick specified keys in this array from fn result
|
* _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
|
### Example
|
||||||
|
|
||||||
@ -46,6 +47,10 @@ const { data } = await useAsyncData('count', () => $fetch('/api/count'))
|
|||||||
</template>
|
</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`
|
## `useFetch`
|
||||||
|
|
||||||
Within your pages, components and plugins you can use `useFetch` to get universally fetch from any URL.
|
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
|
* `params`: Query params
|
||||||
* `baseURL`: Base URL for request
|
* `baseURL`: Base URL for request
|
||||||
* Options from `useAsyncData`
|
* Options from `useAsyncData`
|
||||||
* `defer`
|
* `lazy`
|
||||||
* `server`
|
* `server`
|
||||||
|
* `default`
|
||||||
* `pick`
|
* `pick`
|
||||||
* `transform`
|
* `transform`
|
||||||
|
|
||||||
@ -87,9 +93,13 @@ const { data } = await useFetch('/api/count')
|
|||||||
</template>
|
</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=👉}
|
::alert{icon=👉}
|
||||||
**We strongly recommend you only select the keys that you will use in your component.**
|
**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>
|
</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.
|
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",
|
"hash-sum": "^2.0.0",
|
||||||
"magic-string": "^0.25.7",
|
"magic-string": "^0.25.7",
|
||||||
"mlly": "^0.3.13",
|
"mlly": "^0.3.13",
|
||||||
|
"murmurhash-es": "^0.1.1",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"nuxi": "3.0.0",
|
"nuxi": "3.0.0",
|
||||||
"p-debounce": "^4.0.0",
|
"p-debounce": "^4.0.0",
|
||||||
|
@ -2,6 +2,22 @@ import Vue from 'vue'
|
|||||||
import { createHooks } from 'hookable'
|
import { createHooks } from 'hookable'
|
||||||
import { setNuxtAppInstance } from '#app'
|
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) => {
|
export default (ctx, inject) => {
|
||||||
const nuxtApp = {
|
const nuxtApp = {
|
||||||
vueApp: {
|
vueApp: {
|
||||||
@ -21,7 +37,8 @@ export default (ctx, inject) => {
|
|||||||
},
|
},
|
||||||
provide: inject,
|
provide: inject,
|
||||||
globalName: 'nuxt',
|
globalName: 'nuxt',
|
||||||
payload: process.client ? ctx.nuxtState : ctx.ssrContext.nuxt,
|
payload: proxiedState(process.client ? ctx.nuxtState : ctx.ssrContext.nuxt),
|
||||||
|
_asyncDataPromises: [],
|
||||||
isHydrating: ctx.isHMR,
|
isHydrating: ctx.isHMR,
|
||||||
nuxt2Context: ctx
|
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 type { RuntimeConfig } from '@nuxt/kit'
|
||||||
import { useNuxtApp } from './app'
|
import { useNuxtApp } from './app'
|
||||||
|
|
||||||
|
export { useLazyAsyncData } from './asyncData'
|
||||||
|
export { useLazyFetch } from './fetch'
|
||||||
|
|
||||||
export * from '@vue/composition-api'
|
export * from '@vue/composition-api'
|
||||||
|
|
||||||
const mock = () => () => { throw new Error('not implemented') }
|
const mock = () => () => { throw new Error('not implemented') }
|
||||||
|
|
||||||
export const useAsyncData = mock()
|
export const useAsyncData = mock()
|
||||||
|
export const useFetch = mock()
|
||||||
export const useHydration = mock()
|
export const useHydration = mock()
|
||||||
|
|
||||||
// Runtime config helper
|
// 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 type { Ref } from 'vue'
|
||||||
import { NuxtApp, useNuxtApp } from '#app'
|
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 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>
|
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>
|
PickKeys extends KeyOfRes<_Transform> = KeyOfRes<Transform>
|
||||||
> {
|
> {
|
||||||
server?: boolean
|
server?: boolean
|
||||||
defer?: boolean
|
lazy?: boolean
|
||||||
default?: () => DataT
|
default?: () => DataT
|
||||||
transform?: Transform
|
transform?: Transform
|
||||||
pick?: PickKeys
|
pick?: PickKeys
|
||||||
@ -39,7 +39,7 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||||
options: AsyncDataOptions<DataT, Transform, PickKeys> = {}
|
options: AsyncDataOptions<DataT, Transform, PickKeys> = {}
|
||||||
) : AsyncData<PickFrom<ReturnType<Transform>, PickKeys>> {
|
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>> {
|
||||||
// Validate arguments
|
// Validate arguments
|
||||||
if (typeof key !== 'string') {
|
if (typeof key !== 'string') {
|
||||||
throw new TypeError('asyncData key must be a string')
|
throw new TypeError('asyncData key must be a string')
|
||||||
@ -49,7 +49,12 @@ export function useAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply defaults
|
// 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
|
// Setup nuxt instance payload
|
||||||
const nuxt = useNuxtApp()
|
const nuxt = useNuxtApp()
|
||||||
@ -109,7 +114,8 @@ export function useAsyncData<
|
|||||||
|
|
||||||
// Server side
|
// Server side
|
||||||
if (process.server && fetchOnServer) {
|
if (process.server && fetchOnServer) {
|
||||||
asyncData.refresh()
|
const promise = asyncData.refresh()
|
||||||
|
onServerPrefetch(() => promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client side
|
// Client side
|
||||||
@ -120,14 +126,14 @@ export function useAsyncData<
|
|||||||
}
|
}
|
||||||
// 2. Initial load (server: false): fetch on mounted
|
// 2. Initial load (server: false): fetch on mounted
|
||||||
if (nuxt.isHydrating && clientOnly) {
|
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)
|
instance._nuxtOnBeforeMountCbs.push(asyncData.refresh)
|
||||||
} else if (!nuxt.isHydrating) { // Navigation
|
} else if (!nuxt.isHydrating) { // Navigation
|
||||||
if (options.defer) {
|
if (options.lazy) {
|
||||||
// 3. Navigation (defer: true): fetch on mounted
|
// 3. Navigation (lazy: true): fetch on mounted
|
||||||
instance._nuxtOnBeforeMountCbs.push(asyncData.refresh)
|
instance._nuxtOnBeforeMountCbs.push(asyncData.refresh)
|
||||||
} else {
|
} else {
|
||||||
// 4. Navigation (defer: false): await fetch
|
// 4. Navigation (lazy: false): await fetch
|
||||||
asyncData.refresh()
|
asyncData.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +147,18 @@ export function useAsyncData<
|
|||||||
return asyncDataPromise as AsyncData<DataT>
|
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[]) {
|
function pick (obj: Record<string, any>, keys: string[]) {
|
||||||
const newObj = {}
|
const newObj = {}
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
|
@ -13,11 +13,11 @@ export type UseFetchOptions<
|
|||||||
> = AsyncDataOptions<DataT, Transform, PickKeys> & FetchOptions & { key?: string }
|
> = AsyncDataOptions<DataT, Transform, PickKeys> & FetchOptions & { key?: string }
|
||||||
|
|
||||||
export function useFetch<
|
export function useFetch<
|
||||||
ReqT extends string = string,
|
ReqT extends string = string,
|
||||||
ResT = FetchResult<ReqT>,
|
ResT = FetchResult<ReqT>,
|
||||||
Transform extends (res: ResT) => any = (res: ResT) => ResT,
|
Transform extends (res: ResT) => any = (res: ResT) => ResT,
|
||||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||||
> (
|
> (
|
||||||
url: ReqT,
|
url: ReqT,
|
||||||
opts: UseFetchOptions<ResT, Transform, PickKeys> = {}
|
opts: UseFetchOptions<ResT, Transform, PickKeys> = {}
|
||||||
) {
|
) {
|
||||||
@ -38,6 +38,18 @@ export function useFetch<
|
|||||||
return useAsyncData(opts.key, () => $fetch(url, opts) as Promise<ResT>, opts)
|
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) {
|
function generateKey (keys) {
|
||||||
return '$f' + murmurHashV3(JSON.stringify(keys))
|
return '$f' + murmurHashV3(JSON.stringify(keys))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { defineNuxtComponent } from './component'
|
export { defineNuxtComponent } from './component'
|
||||||
export { useAsyncData } from './asyncData'
|
export { useAsyncData, useLazyAsyncData } from './asyncData'
|
||||||
export { useHydration } from './hydrate'
|
export { useHydration } from './hydrate'
|
||||||
export { useState } from './state'
|
export { useState } from './state'
|
||||||
export { useFetch } from './fetch'
|
export { useFetch, useLazyFetch } from './fetch'
|
||||||
|
@ -6,12 +6,14 @@ export const Nuxt3AutoImports: AutoImportSource[] = [
|
|||||||
from: '#app',
|
from: '#app',
|
||||||
names: [
|
names: [
|
||||||
'useAsyncData',
|
'useAsyncData',
|
||||||
|
'useLazyAsyncData',
|
||||||
'defineNuxtComponent',
|
'defineNuxtComponent',
|
||||||
'useNuxtApp',
|
'useNuxtApp',
|
||||||
'defineNuxtPlugin',
|
'defineNuxtPlugin',
|
||||||
'useRuntimeConfig',
|
'useRuntimeConfig',
|
||||||
'useState',
|
'useState',
|
||||||
'useFetch'
|
'useFetch',
|
||||||
|
'useLazyFetch'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// #meta
|
// #meta
|
||||||
|
Loading…
Reference in New Issue
Block a user