mirror of
https://github.com/vuejs/vue.git
synced 2024-11-21 20:28:54 +00:00
feat: types for <script setup>
macros
This commit is contained in:
parent
3c2707b62e
commit
7173ad4272
@ -467,7 +467,7 @@ export function resolveAsset(
|
||||
// fallback to prototype chain
|
||||
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
|
||||
if (__DEV__ && warnMissing && !res) {
|
||||
warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id, options)
|
||||
warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -26,7 +26,9 @@ export type ComponentOptions = {
|
||||
|
||||
// data
|
||||
data: object | Function | void
|
||||
props?: { [key: string]: PropOptions }
|
||||
props?:
|
||||
| string[]
|
||||
| Record<string, Function | Array<Function> | null | PropOptions>
|
||||
propsData?: object
|
||||
computed?: {
|
||||
[key: string]:
|
||||
@ -105,8 +107,8 @@ export type ComponentOptions = {
|
||||
}
|
||||
|
||||
export type PropOptions = {
|
||||
type: Function | Array<Function> | null
|
||||
default: any
|
||||
required: boolean | null
|
||||
validator: Function | null
|
||||
type?: Function | Array<Function> | null
|
||||
default?: any
|
||||
required?: boolean | null
|
||||
validator?: Function | null
|
||||
}
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { Component } from 'types/component'
|
||||
import { PropOptions } from 'types/options'
|
||||
import { def, invokeWithErrorHandling, isReserved, warn } from '../core/util'
|
||||
import VNode from '../core/vdom/vnode'
|
||||
import { bind, emptyObject, isFunction, isObject } from '../shared/util'
|
||||
import {
|
||||
bind,
|
||||
emptyObject,
|
||||
isArray,
|
||||
isFunction,
|
||||
isObject
|
||||
} from '../shared/util'
|
||||
import { currentInstance, setCurrentInstance } from './currentInstance'
|
||||
import { isRef } from './reactivity/ref'
|
||||
|
||||
@ -193,3 +200,35 @@ function getContext(): SetupContext {
|
||||
const vm = currentInstance!
|
||||
return vm._setupContext || (vm._setupContext = createSetupContext(vm))
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime helper for merging default declarations. Imported by compiled code
|
||||
* only.
|
||||
* @internal
|
||||
*/
|
||||
export function mergeDefaults(
|
||||
raw: string[] | Record<string, PropOptions>,
|
||||
defaults: Record<string, any>
|
||||
): Record<string, PropOptions> {
|
||||
const props = isArray(raw)
|
||||
? raw.reduce(
|
||||
(normalized, p) => ((normalized[p] = {}), normalized),
|
||||
{} as Record<string, PropOptions>
|
||||
)
|
||||
: raw
|
||||
for (const key in defaults) {
|
||||
const opt = props[key]
|
||||
if (opt) {
|
||||
if (isArray(opt) || isFunction(opt)) {
|
||||
props[key] = { type: opt, default: defaults[key] }
|
||||
} else {
|
||||
opt.default = defaults[key]
|
||||
}
|
||||
} else if (opt === null) {
|
||||
props[key] = { default: defaults[key] }
|
||||
} else if (__DEV__) {
|
||||
warn(`props default key "${key}" has no corresponding declaration.`)
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
@ -179,10 +179,8 @@ function doWatch(
|
||||
|
||||
const warnInvalidSource = (s: unknown) => {
|
||||
warn(
|
||||
`Invalid watch source: `,
|
||||
s,
|
||||
`A watch source can only be a getter/effect function, a ref, ` +
|
||||
`a reactive object, or an array of these types.`
|
||||
`Invalid watch source: ${s}. A watch source can only be a getter/effect ` +
|
||||
`function, a ref, a reactive object, or an array of these types.`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ export { provide, inject, InjectionKey } from './apiInject'
|
||||
|
||||
export { h } from './h'
|
||||
export { getCurrentInstance } from './currentInstance'
|
||||
export { useSlots, useAttrs } from './apiSetup'
|
||||
export { useSlots, useAttrs, mergeDefaults } from './apiSetup'
|
||||
export { nextTick } from 'core/util/next-tick'
|
||||
export { set, del } from 'core/observer'
|
||||
|
||||
|
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
@ -35,6 +35,8 @@ export {
|
||||
|
||||
export * from './v3-manual-apis'
|
||||
export * from './v3-generated'
|
||||
// <script setup> helpers
|
||||
export * from './v3-setup-helpers'
|
||||
|
||||
export { Data } from './common'
|
||||
export { SetupContext } from './v3-setup-context'
|
||||
|
134
types/test/setup-helpers-test.ts
Normal file
134
types/test/setup-helpers-test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { useAttrs, useSlots, SetupContext } from '../index'
|
||||
import { describe, expectType } from './utils'
|
||||
|
||||
describe('defineProps w/ type declaration', () => {
|
||||
// type declaration
|
||||
const props = defineProps<{
|
||||
foo: string
|
||||
}>()
|
||||
// explicitly declared type should be refined
|
||||
expectType<string>(props.foo)
|
||||
// @ts-expect-error
|
||||
props.bar
|
||||
})
|
||||
|
||||
describe('defineProps w/ type declaration + withDefaults', () => {
|
||||
const res = withDefaults(
|
||||
defineProps<{
|
||||
number?: number
|
||||
arr?: string[]
|
||||
obj?: { x: number }
|
||||
fn?: (e: string) => void
|
||||
x?: string
|
||||
genStr?: string
|
||||
}>(),
|
||||
{
|
||||
number: 123,
|
||||
arr: () => [],
|
||||
obj: () => ({ x: 123 }),
|
||||
fn: () => {},
|
||||
genStr: () => ''
|
||||
}
|
||||
)
|
||||
|
||||
res.number + 1
|
||||
res.arr.push('hi')
|
||||
res.obj.x
|
||||
res.fn('hi')
|
||||
// @ts-expect-error
|
||||
res.x.slice()
|
||||
res.genStr.slice()
|
||||
})
|
||||
|
||||
describe('defineProps w/ union type declaration + withDefaults', () => {
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
union1?: number | number[] | { x: number }
|
||||
union2?: number | number[] | { x: number }
|
||||
union3?: number | number[] | { x: number }
|
||||
union4?: number | number[] | { x: number }
|
||||
}>(),
|
||||
{
|
||||
union1: 123,
|
||||
union2: () => [123],
|
||||
union3: () => ({ x: 123 }),
|
||||
union4: () => 123
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('defineProps w/ runtime declaration', () => {
|
||||
// runtime declaration
|
||||
const props = defineProps({
|
||||
foo: String,
|
||||
bar: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
baz: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
expectType<{
|
||||
foo?: string
|
||||
bar: number
|
||||
baz: unknown[]
|
||||
}>(props)
|
||||
|
||||
props.foo && props.foo + 'bar'
|
||||
props.bar + 1
|
||||
// @ts-expect-error should be readonly
|
||||
props.bar++
|
||||
props.baz.push(1)
|
||||
|
||||
const props2 = defineProps(['foo', 'bar'])
|
||||
props2.foo + props2.bar
|
||||
// @ts-expect-error
|
||||
props2.baz
|
||||
})
|
||||
|
||||
describe('defineEmits w/ type declaration', () => {
|
||||
const emit = defineEmits<(e: 'change') => void>()
|
||||
emit('change')
|
||||
// @ts-expect-error
|
||||
emit()
|
||||
// @ts-expect-error
|
||||
emit('bar')
|
||||
|
||||
type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
|
||||
const emit2 = defineEmits<Emits>()
|
||||
|
||||
emit2('foo')
|
||||
emit2('bar')
|
||||
emit2('baz', 123)
|
||||
// @ts-expect-error
|
||||
emit2('baz')
|
||||
})
|
||||
|
||||
describe('defineEmits w/ runtime declaration', () => {
|
||||
const emit = defineEmits({
|
||||
foo: () => {},
|
||||
bar: null
|
||||
})
|
||||
emit('foo')
|
||||
emit('bar', 123)
|
||||
// @ts-expect-error
|
||||
emit('baz')
|
||||
|
||||
const emit2 = defineEmits(['foo', 'bar'])
|
||||
emit2('foo')
|
||||
emit2('bar', 123)
|
||||
// @ts-expect-error
|
||||
emit2('baz')
|
||||
})
|
||||
|
||||
describe('useAttrs', () => {
|
||||
const attrs = useAttrs()
|
||||
expectType<Record<string, unknown>>(attrs)
|
||||
})
|
||||
|
||||
describe('useSlots', () => {
|
||||
const slots = useSlots()
|
||||
expectType<SetupContext['slots']>(slots)
|
||||
})
|
150
types/v3-setup-helpers.d.ts
vendored
Normal file
150
types/v3-setup-helpers.d.ts
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
import { EmitFn, EmitsOptions } from './v3-setup-context'
|
||||
import {
|
||||
ComponentObjectPropsOptions,
|
||||
ExtractPropTypes
|
||||
} from './v3-component-props'
|
||||
|
||||
/**
|
||||
* 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 declaration:
|
||||
* ```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: 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<
|
||||
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
|
||||
>(props: PP): Readonly<ExtractPropTypes<PP>>
|
||||
// overload 3: typed-based declaration
|
||||
export function defineProps<TypeProps>(): Readonly<TypeProps>
|
||||
|
||||
/**
|
||||
* 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 declaration:
|
||||
* ```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
|
||||
|
||||
/**
|
||||
* 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. variables 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 extends Record<string, any> = Record<string, any>
|
||||
>(exposed?: Exposed): void
|
||||
|
||||
type NotUndefined<T> = T extends undefined ? never : T
|
||||
|
||||
type InferDefaults<T> = {
|
||||
[K in keyof T]?: InferDefault<T, NotUndefined<T[K]>>
|
||||
}
|
||||
|
||||
type InferDefault<P, T> = T extends
|
||||
| null
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| symbol
|
||||
| Function
|
||||
? T | ((props: P) => T)
|
||||
: (props: P) => T
|
||||
|
||||
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` declaration.
|
||||
*
|
||||
* 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>
|
||||
|
||||
// make them global
|
||||
type _defineProps = typeof defineProps
|
||||
type _defineEmits = typeof defineEmits
|
||||
type _defineExpose = typeof defineExpose
|
||||
type _withDefaults = typeof withDefaults
|
||||
|
||||
declare global {
|
||||
const defineProps: _defineProps
|
||||
const defineEmits: _defineEmits
|
||||
const defineExpose: _defineExpose
|
||||
const withDefaults: _withDefaults
|
||||
}
|
Loading…
Reference in New Issue
Block a user