feat(compiler-sfc): <script setup> defineProps destructure transform (#4690)
This commit is contained in:
@@ -11,7 +11,8 @@ import {
|
||||
SetupContext,
|
||||
Suspense,
|
||||
computed,
|
||||
ComputedRef
|
||||
ComputedRef,
|
||||
shallowReactive
|
||||
} from '@vue/runtime-test'
|
||||
import {
|
||||
defineEmits,
|
||||
@@ -21,7 +22,8 @@ import {
|
||||
useAttrs,
|
||||
useSlots,
|
||||
mergeDefaults,
|
||||
withAsyncContext
|
||||
withAsyncContext,
|
||||
createPropsRestProxy
|
||||
} from '../src/apiSetupHelpers'
|
||||
|
||||
describe('SFC <script setup> helpers', () => {
|
||||
@@ -77,26 +79,62 @@ describe('SFC <script setup> helpers', () => {
|
||||
expect(attrs).toBe(ctx!.attrs)
|
||||
})
|
||||
|
||||
test('mergeDefaults', () => {
|
||||
const merged = mergeDefaults(
|
||||
{
|
||||
foo: null,
|
||||
bar: { type: String, required: false }
|
||||
},
|
||||
{
|
||||
foo: 1,
|
||||
bar: 'baz'
|
||||
}
|
||||
)
|
||||
expect(merged).toMatchObject({
|
||||
foo: { default: 1 },
|
||||
bar: { type: String, required: false, default: 'baz' }
|
||||
describe('mergeDefaults', () => {
|
||||
test('object syntax', () => {
|
||||
const merged = mergeDefaults(
|
||||
{
|
||||
foo: null,
|
||||
bar: { type: String, required: false },
|
||||
baz: String
|
||||
},
|
||||
{
|
||||
foo: 1,
|
||||
bar: 'baz',
|
||||
baz: 'qux'
|
||||
}
|
||||
)
|
||||
expect(merged).toMatchObject({
|
||||
foo: { default: 1 },
|
||||
bar: { type: String, required: false, default: 'baz' },
|
||||
baz: { type: String, default: 'qux' }
|
||||
})
|
||||
})
|
||||
|
||||
mergeDefaults({}, { foo: 1 })
|
||||
expect(
|
||||
`props default key "foo" has no corresponding declaration`
|
||||
).toHaveBeenWarned()
|
||||
test('array syntax', () => {
|
||||
const merged = mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
foo: 1,
|
||||
bar: 'baz',
|
||||
baz: 'qux'
|
||||
})
|
||||
expect(merged).toMatchObject({
|
||||
foo: { default: 1 },
|
||||
bar: { default: 'baz' },
|
||||
baz: { default: 'qux' }
|
||||
})
|
||||
})
|
||||
|
||||
test('should warn missing', () => {
|
||||
mergeDefaults({}, { foo: 1 })
|
||||
expect(
|
||||
`props default key "foo" has no corresponding declaration`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
|
||||
describe('createPropsRestProxy', () => {
|
||||
const original = shallowReactive({
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
baz: 3
|
||||
})
|
||||
const rest = createPropsRestProxy(original, ['foo', 'bar'])
|
||||
expect('foo' in rest).toBe(false)
|
||||
expect('bar' in rest).toBe(false)
|
||||
expect(rest.baz).toBe(3)
|
||||
expect(Object.keys(rest)).toEqual(['baz'])
|
||||
|
||||
original.baz = 4
|
||||
expect(rest.baz).toBe(4)
|
||||
})
|
||||
|
||||
describe('withAsyncContext', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPromise } from '../../shared/src'
|
||||
import { ComponentPropsOptions } from '@vue/runtime-core'
|
||||
import { isArray, isPromise, isFunction } from '@vue/shared'
|
||||
import {
|
||||
getCurrentInstance,
|
||||
setCurrentInstance,
|
||||
@@ -7,11 +8,7 @@ import {
|
||||
unsetCurrentInstance
|
||||
} from './component'
|
||||
import { EmitFn, EmitsOptions } from './componentEmits'
|
||||
import {
|
||||
ComponentObjectPropsOptions,
|
||||
PropOptions,
|
||||
ExtractPropTypes
|
||||
} from './componentProps'
|
||||
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
|
||||
import { warn } from './warning'
|
||||
|
||||
// dev only
|
||||
@@ -195,15 +192,24 @@ function getContext(): SetupContext {
|
||||
* @internal
|
||||
*/
|
||||
export function mergeDefaults(
|
||||
// the base props is compiler-generated and guaranteed to be in this shape.
|
||||
props: Record<string, PropOptions | null>,
|
||||
raw: ComponentPropsOptions,
|
||||
defaults: Record<string, any>
|
||||
) {
|
||||
): ComponentObjectPropsOptions {
|
||||
const props = isArray(raw)
|
||||
? raw.reduce(
|
||||
(normalized, p) => ((normalized[p] = {}), normalized),
|
||||
{} as ComponentObjectPropsOptions
|
||||
)
|
||||
: raw
|
||||
for (const key in defaults) {
|
||||
const val = props[key]
|
||||
if (val) {
|
||||
val.default = defaults[key]
|
||||
} else if (val === null) {
|
||||
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.`)
|
||||
@@ -212,6 +218,27 @@ export function mergeDefaults(
|
||||
return props
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a proxy for the rest element when destructuring props with
|
||||
* defineProps().
|
||||
* @internal
|
||||
*/
|
||||
export function createPropsRestProxy(
|
||||
props: any,
|
||||
excludedKeys: string[]
|
||||
): Record<string, any> {
|
||||
const ret: Record<string, any> = {}
|
||||
for (const key in props) {
|
||||
if (!excludedKeys.includes(key)) {
|
||||
Object.defineProperty(ret, key, {
|
||||
enumerable: true,
|
||||
get: () => props[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* `<script setup>` helper for persisting the current instance context over
|
||||
* async/await flows.
|
||||
|
||||
@@ -70,6 +70,7 @@ export {
|
||||
withDefaults,
|
||||
// internal
|
||||
mergeDefaults,
|
||||
createPropsRestProxy,
|
||||
withAsyncContext
|
||||
} from './apiSetupHelpers'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user