Explorar o código

feat(runtime-core): add skipCheck for prop (#7548)

三咲智子 Kevin Deng %!s(int64=3) %!d(string=hai) anos
pai
achega
63ad77f6f6

+ 3 - 1
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

@@ -1715,7 +1715,9 @@ export default /*#__PURE__*/_defineComponent({
     foo: { type: [Function, null], required: true },
     unknown: { type: null, required: true },
     unknownUnion: { type: null, required: true },
-    unknownIntersection: { type: Object, required: true }
+    unknownIntersection: { type: Object, required: true },
+    unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },
+    unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }
   },
   setup(__props: any, { expose: __expose }) {
   __expose();

+ 12 - 2
packages/compiler-sfc/__tests__/compileScript.spec.ts

@@ -1042,6 +1042,8 @@ const emit = defineEmits(['a', 'b'])
         unknown: UnknownType
         unknownUnion: UnknownType | string
         unknownIntersection: UnknownType & Object
+        unknownUnionWithBoolean: UnknownType | boolean
+        unknownUnionWithFunction: UnknownType | (() => any)
       }>()
       </script>`)
       assertCode(content)
@@ -1093,7 +1095,13 @@ const emit = defineEmits(['a', 'b'])
       expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
       // intersection containing unknown type: narrow to the known types
       expect(content).toMatch(
-        `unknownIntersection: { type: Object, required: true }`
+        `unknownIntersection: { type: Object, required: true },`
+      )
+      expect(content).toMatch(
+        `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`
+      )
+      expect(content).toMatch(
+        `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`
       )
       expect(bindings).toStrictEqual({
         string: BindingTypes.PROPS,
@@ -1131,7 +1139,9 @@ const emit = defineEmits(['a', 'b'])
         nonNull: BindingTypes.PROPS,
         unknown: BindingTypes.PROPS,
         unknownUnion: BindingTypes.PROPS,
-        unknownIntersection: BindingTypes.PROPS
+        unknownIntersection: BindingTypes.PROPS,
+        unknownUnionWithBoolean: BindingTypes.PROPS,
+        unknownUnionWithFunction: BindingTypes.PROPS
       })
     })
 

+ 12 - 4
packages/compiler-sfc/src/compileScript.ts

@@ -888,11 +888,11 @@ export function compileScript(
           }
         }
 
-        const { type, required } = props[key]
+        const { type, required, skipCheck } = props[key]
         if (!isProd) {
           return `${key}: { type: ${toRuntimeTypeString(
             type
-          )}, required: ${required}${
+          )}, required: ${required}${skipCheck ? ', skipCheck: true' : ''}${
             defaultString ? `, ${defaultString}` : ``
           } }`
         } else if (
@@ -1964,6 +1964,7 @@ interface PropTypeData {
   key: string
   type: string[]
   required: boolean
+  skipCheck: boolean
 }
 
 function recordType(node: Node, declaredTypes: Record<string, string[]>) {
@@ -1993,19 +1994,26 @@ function extractRuntimeProps(
       m.key.type === 'Identifier'
     ) {
       let type: string[] | undefined
+      let skipCheck = false
       if (m.type === 'TSMethodSignature') {
         type = ['Function']
       } else if (m.typeAnnotation) {
         type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
         // skip check for result containing unknown types
         if (type.includes(UNKNOWN_TYPE)) {
-          type = [`null`]
+          if (type.includes('Boolean') || type.includes('Function')) {
+            type = type.filter(t => t !== UNKNOWN_TYPE)
+            skipCheck = true
+          } else {
+            type = ['null']
+          }
         }
       }
       props[m.key.name] = {
         key: m.key.name,
         required: !m.optional,
-        type: type || [`null`]
+        type: type || [`null`],
+        skipCheck
       }
     }
   }

+ 7 - 2
packages/runtime-core/__tests__/componentProps.spec.ts

@@ -335,7 +335,8 @@ describe('component props', () => {
         arr: { type: Array },
         obj: { type: Object },
         cls: { type: MyClass },
-        fn: { type: Function }
+        fn: { type: Function },
+        skipCheck: { type: [Boolean, Function], skipCheck: true }
       },
       setup() {
         return () => null
@@ -349,7 +350,8 @@ describe('component props', () => {
         arr: {},
         obj: 'false',
         cls: {},
-        fn: true
+        fn: true,
+        skipCheck: 'foo'
       }),
       nodeOps.createElement('div')
     )
@@ -374,6 +376,9 @@ describe('component props', () => {
     expect(
       `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object`
     ).toHaveBeenWarned()
+    expect(
+      `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`
+    ).not.toHaveBeenWarned()
   })
 
   // #3495

+ 3 - 2
packages/runtime-core/src/componentProps.ts

@@ -58,6 +58,7 @@ export interface PropOptions<T = any, D = T> {
   required?: boolean
   default?: D | DefaultFactory<D> | null | undefined | object
   validator?(value: unknown): boolean
+  skipCheck?: boolean
 }
 
 export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
@@ -608,7 +609,7 @@ function validateProp(
   prop: PropOptions,
   isAbsent: boolean
 ) {
-  const { type, required, validator } = prop
+  const { type, required, validator, skipCheck } = prop
   // required!
   if (required && isAbsent) {
     warn('Missing required prop: "' + name + '"')
@@ -619,7 +620,7 @@ function validateProp(
     return
   }
   // type check
-  if (type != null && type !== true) {
+  if (type != null && type !== true && !skipCheck) {
     let isValid = false
     const types = isArray(type) ? type : [type]
     const expectedTypes = []