mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt3): allow providing a ref as default value (#4326)
This commit is contained in:
parent
be298e9216
commit
ee93659147
@ -14,7 +14,7 @@ function useAsyncData(
|
|||||||
type AsyncDataOptions = {
|
type AsyncDataOptions = {
|
||||||
server?: boolean
|
server?: boolean
|
||||||
lazy?: boolean
|
lazy?: boolean
|
||||||
default?: () => DataT
|
default?: () => DataT | Ref<DataT>
|
||||||
transform?: (input: DataT) => DataT
|
transform?: (input: DataT) => DataT
|
||||||
pick?: string[]
|
pick?: string[]
|
||||||
watch?: WatchSource[]
|
watch?: WatchSource[]
|
||||||
@ -41,6 +41,7 @@ type DataT = {
|
|||||||
* _pick_: only pick specified keys in this array from `handler` function result
|
* _pick_: only pick specified keys in this array from `handler` function result
|
||||||
* _watch_: watch reactive sources to auto refresh
|
* _watch_: watch reactive sources to auto refresh
|
||||||
* _initialCache_: When set to `false`, will skip payload cache for initial fetch. (defaults to `true`)
|
* _initialCache_: When set to `false`, will skip payload cache for initial fetch. (defaults to `true`)
|
||||||
|
* _default_: A function that returns the default value (before the handler function returns its value).
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
@ -132,6 +132,10 @@ The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/de
|
|||||||
be returned as the cookie's value.
|
be returned as the cookie's value.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### `default`
|
||||||
|
|
||||||
|
Specifies a function that returns the cookie's default value. The function can also return a `Ref`.
|
||||||
|
|
||||||
## Handling cookies in API routes
|
## Handling cookies in API routes
|
||||||
|
|
||||||
You can use `useCookie` and `setCookie` from [`h3`](https://github.com/unjs/h3) package to set cookies in server API routes.
|
You can use `useCookie` and `setCookie` from [`h3`](https://github.com/unjs/h3) package to set cookies in server API routes.
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# `useState`
|
# `useState`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
useState<T>(key: string, init?: () => T): Ref<T>
|
useState<T>(key: string, init?: () => T | Ref<T>): Ref<T>
|
||||||
```
|
```
|
||||||
|
|
||||||
* **key**: A unique key ensuring that data fetching can be properly de-duplicated across requests
|
* **key**: A unique key ensuring that data fetching can be properly de-duplicated across requests
|
||||||
* **init**: A function that provides initial value for the state when it's not initiated
|
* **init**: A function that provides initial value for the state when it's not initiated. This function can also return a `Ref`.
|
||||||
* **T**: (typescript only) Specify the type of state
|
* **T**: (typescript only) Specify the type of state
|
||||||
|
|
||||||
::ReadMore{link="/guide/features/state-management"}
|
::ReadMore{link="/guide/features/state-management"}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { onBeforeMount, onServerPrefetch, onUnmounted, ref, getCurrentInstance, watch } from 'vue'
|
import { onBeforeMount, onServerPrefetch, onUnmounted, ref, getCurrentInstance, watch } from 'vue'
|
||||||
import type { Ref, WatchSource } from 'vue'
|
import type { Ref, WatchSource } from 'vue'
|
||||||
|
import { wrapInRef } from './utils'
|
||||||
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
|
||||||
@ -17,7 +18,7 @@ export interface AsyncDataOptions<
|
|||||||
> {
|
> {
|
||||||
server?: boolean
|
server?: boolean
|
||||||
lazy?: boolean
|
lazy?: boolean
|
||||||
default?: () => DataT
|
default?: () => DataT | Ref<DataT>
|
||||||
transform?: Transform
|
transform?: Transform
|
||||||
pick?: PickKeys
|
pick?: PickKeys
|
||||||
watch?: MultiWatchSources
|
watch?: MultiWatchSources
|
||||||
@ -85,7 +86,7 @@ export function useAsyncData<
|
|||||||
const useInitialCache = () => options.initialCache && nuxt.payload.data[key] !== undefined
|
const useInitialCache = () => options.initialCache && nuxt.payload.data[key] !== undefined
|
||||||
|
|
||||||
const asyncData = {
|
const asyncData = {
|
||||||
data: ref(nuxt.payload.data[key] ?? options.default()),
|
data: wrapInRef(nuxt.payload.data[key] ?? options.default()),
|
||||||
pending: ref(!useInitialCache()),
|
pending: ref(!useInitialCache()),
|
||||||
error: ref(nuxt.payload._errors[key] ?? null)
|
error: ref(nuxt.payload._errors[key] ?? null)
|
||||||
} as AsyncData<DataT, DataE>
|
} as AsyncData<DataT, DataE>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Ref, ref, watch } from 'vue'
|
import { Ref, watch } from 'vue'
|
||||||
import { parse, serialize, CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
import { parse, serialize, CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
||||||
import { appendHeader } from 'h3'
|
import { appendHeader } from 'h3'
|
||||||
import type { CompatibilityEvent } from 'h3'
|
import type { CompatibilityEvent } from 'h3'
|
||||||
import destr from 'destr'
|
import destr from 'destr'
|
||||||
import { useRequestEvent } from './ssr'
|
import { useRequestEvent } from './ssr'
|
||||||
|
import { wrapInRef } from './utils'
|
||||||
import { useNuxtApp } from '#app'
|
import { useNuxtApp } from '#app'
|
||||||
|
|
||||||
type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'>
|
type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'>
|
||||||
@ -11,7 +12,7 @@ type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode'
|
|||||||
export interface CookieOptions<T=any> extends _CookieOptions {
|
export interface CookieOptions<T=any> extends _CookieOptions {
|
||||||
decode?(value: string): T
|
decode?(value: string): T
|
||||||
encode?(value: T): string;
|
encode?(value: T): string;
|
||||||
default?: () => T
|
default?: () => T | Ref<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CookieRef<T> extends Ref<T> {}
|
export interface CookieRef<T> extends Ref<T> {}
|
||||||
@ -26,7 +27,7 @@ export function useCookie <T=string> (name: string, _opts?: CookieOptions<T>): C
|
|||||||
const opts = { ...CookieDefaults, ..._opts }
|
const opts = { ...CookieDefaults, ..._opts }
|
||||||
const cookies = readRawCookies(opts)
|
const cookies = readRawCookies(opts)
|
||||||
|
|
||||||
const cookie = ref(cookies[name] ?? opts.default?.())
|
const cookie = wrapInRef<T>(cookies[name] ?? opts.default?.())
|
||||||
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
watch(cookie, () => { writeClientCookie(name, cookie.value, opts as CookieSerializeOptions) })
|
watch(cookie, () => { writeClientCookie(name, cookie.value, opts as CookieSerializeOptions) })
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { toRef } from 'vue'
|
import { isRef, toRef } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { useNuxtApp } from '#app'
|
import { useNuxtApp } from '#app'
|
||||||
|
|
||||||
@ -8,11 +8,17 @@ import { useNuxtApp } from '#app'
|
|||||||
* @param key a unique key ensuring that data fetching can be properly de-duplicated across requests
|
* @param key a unique key ensuring that data fetching can be properly de-duplicated across requests
|
||||||
* @param init a function that provides initial value for the state when it's not initiated
|
* @param init a function that provides initial value for the state when it's not initiated
|
||||||
*/
|
*/
|
||||||
export const useState = <T> (key: string, init?: (() => T)): Ref<T> => {
|
export const useState = <T> (key: string, init?: (() => T | Ref<T>)): Ref<T> => {
|
||||||
const nuxt = useNuxtApp()
|
const nuxt = useNuxtApp()
|
||||||
const state = toRef(nuxt.payload.state, key)
|
const state = toRef(nuxt.payload.state, key)
|
||||||
if (state.value === undefined && init) {
|
if (state.value === undefined && init) {
|
||||||
state.value = init()
|
const initialValue = init()
|
||||||
|
if (isRef(initialValue)) {
|
||||||
|
// vue will unwrap the ref for us
|
||||||
|
nuxt.payload.state[key] = initialValue
|
||||||
|
return initialValue as Ref<T>
|
||||||
|
}
|
||||||
|
state.value = initialValue
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
3
packages/nuxt3/src/app/composables/utils.ts
Normal file
3
packages/nuxt3/src/app/composables/utils.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { isRef, ref, Ref } from 'vue'
|
||||||
|
|
||||||
|
export const wrapInRef = <T> (value: T | Ref<T>) => isRef(value) ? value : ref(value)
|
20
test/fixtures/basic/types.ts
vendored
20
test/fixtures/basic/types.ts
vendored
@ -104,3 +104,23 @@ describe('runtimeConfig', () => {
|
|||||||
expectTypeOf(runtimeConfig.unknown).toMatchTypeOf<any>()
|
expectTypeOf(runtimeConfig.unknown).toMatchTypeOf<any>()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('composables', () => {
|
||||||
|
it('allows providing default refs', () => {
|
||||||
|
expectTypeOf(useState('test', () => ref('hello'))).toMatchTypeOf<Ref<string>>()
|
||||||
|
expectTypeOf(useState('test', () => 'hello')).toMatchTypeOf<Ref<string>>()
|
||||||
|
|
||||||
|
expectTypeOf(useCookie('test', { default: () => ref(500) })).toMatchTypeOf<Ref<number>>()
|
||||||
|
expectTypeOf(useCookie('test', { default: () => 500 })).toMatchTypeOf<Ref<number>>()
|
||||||
|
|
||||||
|
expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => ref(500) }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => 500 }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
// @ts-expect-error
|
||||||
|
expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => ref(500) }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
// @ts-expect-error
|
||||||
|
expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => 500 }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
|
||||||
|
expectTypeOf(useFetch('test', { default: () => ref(500) }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
expectTypeOf(useFetch('test', { default: () => 500 }).data).toMatchTypeOf<Ref<number>>()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user