feat(compiler-sfc): <script setup> defineProps destructure transform (#4690)

This commit is contained in:
Evan You
2021-09-27 14:24:21 -04:00
committed by GitHub
parent d84d5ecdbd
commit 467e113b95
14 changed files with 717 additions and 124 deletions

View File

@@ -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', () => {

View File

@@ -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.

View File

@@ -70,6 +70,7 @@ export {
withDefaults,
// internal
mergeDefaults,
createPropsRestProxy,
withAsyncContext
} from './apiSetupHelpers'