From a6fd2ee6ac80beb78401c12181c81a597f44fa36 Mon Sep 17 00:00:00 2001 From: Ferdy Budhidharma Date: Wed, 16 Jan 2019 01:30:26 -0600 Subject: [PATCH] [react] better prop typing for JS users (#32182) * better prop resolutions * lint rules --- types/react/index.d.ts | 23 ++++++++++++++++++---- types/react/test/managedAttributes.tsx | 27 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 11d879b2ff..a1e851314d 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -491,6 +491,10 @@ declare namespace React { getDefaultProps?(): P; } + type JSXElementConstructor

= + | ((props: P) => ReactElement | null) + | (new (props: P) => Component); + /** * We use an intersection type to infer multiple type parameters from * a single argument, which is useful for many top-level API defs. @@ -719,8 +723,8 @@ declare namespace React { * NOTE: prefer ComponentPropsWithRef, if the ref is forwarded, * or ComponentPropsWithoutRef when refs are not supported. */ - type ComponentProps = - T extends ComponentType + type ComponentProps> = + T extends JSXElementConstructor ? P : T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] @@ -2629,9 +2633,20 @@ declare namespace React { } } -// Declared props take priority over inferred props +// naked 'any' type in a conditional type will short circuit and union both the then/else branches +// so boolean is only resolved for T = any +type IsExactlyAny = boolean extends (T extends never ? true : false) ? true : false; + +// Try to resolve ill-defined props like for JS users: props can be any, or sometimes objects with properties of type any +// If props is type any, use propTypes definitions, otherwise for each `any` property of props, use the propTypes type // If declared props have indexed properties, ignore inferred props entirely as keyof gets widened -type MergePropTypes = P & Pick>; +type MergePropTypes = IsExactlyAny

extends true ? T : ({ + [K in keyof P]: IsExactlyAny extends true + ? K extends keyof T + ? T[K] + : P[K] + : P[K] +} & Pick>); // Any prop that has a default prop becomes optional, but its type is unchanged // Undeclared default props are augmented into the resulting allowable attributes diff --git a/types/react/test/managedAttributes.tsx b/types/react/test/managedAttributes.tsx index 1e0e8226d9..9939cffb15 100644 --- a/types/react/test/managedAttributes.tsx +++ b/types/react/test/managedAttributes.tsx @@ -222,3 +222,30 @@ interface LeaveMeAloneDtslint { foo: string; } // // $ExpectError the type of ForwardRef.defaultProps stays Partial

anyway even if assigned // // ]; + +// const weakComponentPropTypes = { +// foo: PropTypes.string, +// bar: PropTypes.bool.isRequired +// }; +// interface WeakComponentProps1 { +// foo: any; +// bar: number; +// } +// interface WeakComponentProps2 { +// foo: string; +// bar: any; +// } + +// // $ExpectType true +// type weakComponentTest1 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, any> extends { +// foo?: string | null +// bar: boolean +// } ? true : false; +// type weakComponentTest2 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps1> extends { +// foo?: string | null +// bar: number +// } ? true : false; +// type weakComponentTest3 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps2> extends { +// foo: string +// bar: boolean +// } ? true : false;