feat(sfc): withDefaults helper
This commit is contained in:
@@ -4,63 +4,104 @@ import {
|
||||
createSetupContext
|
||||
} from './component'
|
||||
import { EmitFn, EmitsOptions } from './componentEmits'
|
||||
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
|
||||
import {
|
||||
ComponentObjectPropsOptions,
|
||||
PropOptions,
|
||||
ExtractPropTypes
|
||||
} from './componentProps'
|
||||
import { warn } from './warning'
|
||||
|
||||
type InferDefaults<T> = {
|
||||
[K in keyof T]?: NonNullable<T[K]> extends object
|
||||
? () => NonNullable<T[K]>
|
||||
: NonNullable<T[K]>
|
||||
}
|
||||
// dev only
|
||||
const warnRuntimeUsage = (method: string) =>
|
||||
warn(
|
||||
`${method}() is a compiler-hint helper that is only usable inside ` +
|
||||
`<script setup> of a single file component. Its arguments should be ` +
|
||||
`compiled away and passing it at runtime has no effect.`
|
||||
)
|
||||
|
||||
/**
|
||||
* Compile-time-only helper used for declaring props inside `<script setup>`.
|
||||
* This is stripped away in the compiled code and should never be actually
|
||||
* called at runtime.
|
||||
* Vue `<script setup>` compiler macro for declaring component props. The
|
||||
* expected argument is the same as the component `props` option.
|
||||
*
|
||||
* Example runtime declaration:
|
||||
* ```js
|
||||
* // using Array syntax
|
||||
* const props = defineProps(['foo', 'bar'])
|
||||
* // using Object syntax
|
||||
* const props = defineProps({
|
||||
* foo: String,
|
||||
* bar: {
|
||||
* type: Number,
|
||||
* required: true
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Equivalent type-based decalration:
|
||||
* ```ts
|
||||
* // will be compiled into equivalent runtime declarations
|
||||
* const props = defineProps<{
|
||||
* foo?: string
|
||||
* bar: number
|
||||
* }>()
|
||||
* ```
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the
|
||||
* output and should **not** be actually called at runtime.
|
||||
*/
|
||||
// overload 1: string props
|
||||
// overload 1: runtime props w/ array
|
||||
export function defineProps<PropNames extends string = string>(
|
||||
props: PropNames[]
|
||||
): Readonly<{ [key in PropNames]?: any }>
|
||||
// overload 2: runtime props w/ object
|
||||
export function defineProps<
|
||||
TypeProps = undefined,
|
||||
PropNames extends string = string,
|
||||
InferredProps = { [key in PropNames]?: any }
|
||||
>(
|
||||
props?: PropNames[]
|
||||
): Readonly<TypeProps extends undefined ? InferredProps : TypeProps>
|
||||
// overload 2: object props
|
||||
export function defineProps<
|
||||
TypeProps = undefined,
|
||||
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions,
|
||||
InferredProps = ExtractPropTypes<PP>
|
||||
>(
|
||||
props?: PP,
|
||||
defaults?: InferDefaults<TypeProps>
|
||||
): Readonly<TypeProps extends undefined ? InferredProps : TypeProps>
|
||||
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
|
||||
>(props: PP): Readonly<ExtractPropTypes<PP>>
|
||||
// overload 3: typed-based declaration
|
||||
export function defineProps<TypeProps>(): Readonly<TypeProps>
|
||||
// implementation
|
||||
export function defineProps() {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`defineProps() is a compiler-hint helper that is only usable inside ` +
|
||||
`<script setup> of a single file component. Its arguments should be ` +
|
||||
`compiled away and passing it at runtime has no effect.`
|
||||
)
|
||||
warnRuntimeUsage(`defineProps`)
|
||||
}
|
||||
return null as any
|
||||
}
|
||||
|
||||
export function defineEmits<
|
||||
TypeEmit = undefined,
|
||||
E extends EmitsOptions = EmitsOptions,
|
||||
EE extends string = string,
|
||||
InferredEmit = EmitFn<E>
|
||||
>(emitOptions?: E | EE[]): TypeEmit extends undefined ? InferredEmit : TypeEmit
|
||||
/**
|
||||
* Vue `<script setup>` compiler macro for declaring a component's emitted
|
||||
* events. The expected argument is the same as the component `emits` option.
|
||||
*
|
||||
* Example runtime declaration:
|
||||
* ```js
|
||||
* const emit = defineEmits(['change', 'update'])
|
||||
* ```
|
||||
*
|
||||
* Example type-based decalration:
|
||||
* ```ts
|
||||
* const emit = defineEmits<{
|
||||
* (event: 'change'): void
|
||||
* (event: 'update', id: number): void
|
||||
* }>()
|
||||
*
|
||||
* emit('change')
|
||||
* emit('update', 1)
|
||||
* ```
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the
|
||||
* output and should **not** be actually called at runtime.
|
||||
*/
|
||||
// overload 1: runtime emits w/ array
|
||||
export function defineEmits<EE extends string = string>(
|
||||
emitOptions: EE[]
|
||||
): EmitFn<EE[]>
|
||||
export function defineEmits<E extends EmitsOptions = EmitsOptions>(
|
||||
emitOptions: E
|
||||
): EmitFn<E>
|
||||
export function defineEmits<TypeEmit>(): TypeEmit
|
||||
// implementation
|
||||
export function defineEmits() {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`defineEmits() is a compiler-hint helper that is only usable inside ` +
|
||||
`<script setup> of a single file component. Its arguments should be ` +
|
||||
`compiled away and passing it at runtime has no effect.`
|
||||
)
|
||||
warnRuntimeUsage(`defineEmits`)
|
||||
}
|
||||
return null as any
|
||||
}
|
||||
@@ -70,16 +111,70 @@ export function defineEmits() {
|
||||
*/
|
||||
export const defineEmit = defineEmits
|
||||
|
||||
/**
|
||||
* Vue `<script setup>` compiler macro for declaring a component's exposed
|
||||
* instance properties when it is accessed by a parent component via template
|
||||
* refs.
|
||||
*
|
||||
* `<script setup>` components are closed by default - i.e. varaibles inside
|
||||
* the `<script setup>` scope is not exposed to parent unless explicitly exposed
|
||||
* via `defineExpose`.
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the
|
||||
* output and should **not** be actually called at runtime.
|
||||
*/
|
||||
export function defineExpose(exposed?: Record<string, any>) {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`defineExpose() is a compiler-hint helper that is only usable inside ` +
|
||||
`<script setup> of a single file component. Its usage should be ` +
|
||||
`compiled away and calling it at runtime has no effect.`
|
||||
)
|
||||
warnRuntimeUsage(`defineExpose`)
|
||||
}
|
||||
}
|
||||
|
||||
type NotUndefined<T> = T extends undefined ? never : T
|
||||
|
||||
type InferDefaults<T> = {
|
||||
[K in keyof T]?: NotUndefined<T[K]> extends (
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| symbol
|
||||
| Function)
|
||||
? NotUndefined<T[K]>
|
||||
: (props: T) => NotUndefined<T[K]>
|
||||
}
|
||||
|
||||
type PropsWithDefaults<Base, Defaults> = Base &
|
||||
{
|
||||
[K in keyof Defaults]: K extends keyof Base ? NotUndefined<Base[K]> : never
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue `<script setup>` compiler macro for providing props default values when
|
||||
* using type-based `defineProps` decalration.
|
||||
*
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* withDefaults(defineProps<{
|
||||
* size?: number
|
||||
* labels?: string[]
|
||||
* }>(), {
|
||||
* size: 3,
|
||||
* labels: () => ['default label']
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the output
|
||||
* and should **not** be actually called at runtime.
|
||||
*/
|
||||
export function withDefaults<Props, Defaults extends InferDefaults<Props>>(
|
||||
props: Props,
|
||||
defaults: Defaults
|
||||
): PropsWithDefaults<Props, Defaults> {
|
||||
if (__DEV__) {
|
||||
warnRuntimeUsage(`withDefaults`)
|
||||
}
|
||||
return null as any
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `useSlots` and `useAttrs` instead.
|
||||
*/
|
||||
@@ -93,6 +188,14 @@ export function useContext(): SetupContext {
|
||||
return getContext()
|
||||
}
|
||||
|
||||
export function useSlots(): SetupContext['slots'] {
|
||||
return getContext().slots
|
||||
}
|
||||
|
||||
export function useAttrs(): SetupContext['attrs'] {
|
||||
return getContext().attrs
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
@@ -101,10 +204,25 @@ function getContext(): SetupContext {
|
||||
return i.setupContext || (i.setupContext = createSetupContext(i))
|
||||
}
|
||||
|
||||
export function useSlots(): SetupContext['slots'] {
|
||||
return getContext().slots
|
||||
}
|
||||
|
||||
export function useAttrs(): SetupContext['attrs'] {
|
||||
return getContext().attrs
|
||||
/**
|
||||
* Runtime helper for merging default declarations. Imported by compiled code
|
||||
* only.
|
||||
* @internal
|
||||
*/
|
||||
export function mergeDefaults(
|
||||
// the base props is compiler-generated and guaranteed to be in this shape.
|
||||
props: Record<string, PropOptions | null>,
|
||||
defaults: Record<string, any>
|
||||
) {
|
||||
for (const key in defaults) {
|
||||
const val = props[key]
|
||||
if (val) {
|
||||
val.default = defaults[key]
|
||||
} else if (val === null) {
|
||||
props[key] = { default: defaults[key] }
|
||||
} else if (__DEV__) {
|
||||
warn(`props default key "${key}" has no corresponding declaration.`)
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
|
||||
|
||||
type DefaultFactory<T> = (props: Data) => T | null | undefined
|
||||
|
||||
interface PropOptions<T = any, D = T> {
|
||||
export interface PropOptions<T = any, D = T> {
|
||||
type?: PropType<T> | true | null
|
||||
required?: boolean
|
||||
default?: D | DefaultFactory<D> | null | undefined | object
|
||||
|
||||
@@ -44,9 +44,17 @@ export { provide, inject } from './apiInject'
|
||||
export { nextTick } from './scheduler'
|
||||
export { defineComponent } from './apiDefineComponent'
|
||||
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||
|
||||
// <script setup> API ----------------------------------------------------------
|
||||
|
||||
export {
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineExpose,
|
||||
withDefaults,
|
||||
// internal
|
||||
mergeDefaults,
|
||||
// deprecated
|
||||
defineEmit,
|
||||
useContext
|
||||
} from './apiSetupHelpers'
|
||||
@@ -140,7 +148,6 @@ export {
|
||||
DeepReadonly
|
||||
} from '@vue/reactivity'
|
||||
export {
|
||||
// types
|
||||
WatchEffect,
|
||||
WatchOptions,
|
||||
WatchOptionsBase,
|
||||
|
||||
Reference in New Issue
Block a user