From 67fdf14c908ccfebd5bd9e420fa42d18519401bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eloy=20Dur=C3=A1n?= Date: Fri, 20 Mar 2020 18:44:04 +0100 Subject: [PATCH] [react] Port ElementRef utility type from Flow (#43201) * [react] Port ElementRef utility type from Flow https://flow.org/en/docs/react/types/#toc-react-elementref * [react] Use least amount of required typing * [react] Support forwarded refs with ElementRef * [react] Use correct function component return type * [react] Add ElementRef test for memoized ref forwarding component * [react] Add ElementRef test for lazy component * [react] Match function components with props * [react] Fix test-case by moving type around * [react] Fix flaky test-case * [recharts] Remove deprecated CSS properties These have been removed from `lib.dom.d.ts`: https://github.com/microsoft/TypeScript/pull/37464/files#r395692943 --- types/react/index.d.ts | 40 +++++++++++++++++++++++++++++++++++++-- types/react/test/index.ts | 17 ++++++++++++++++- types/recharts/index.d.ts | 4 ++-- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 645ffafc679015..8486cf8c75e699 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -82,17 +82,53 @@ declare namespace React { | ((props: P) => ReactElement | null) | (new (props: P) => Component); - type Key = string | number; - interface RefObject { readonly current: T | null; } type RefCallback = { bivarianceHack(instance: T | null): void }["bivarianceHack"]; type Ref = RefCallback | RefObject | null; type LegacyRef = string | Ref; + /** + * Gets the instance type for a React element. The instance will be different for various component types: + * + * - React class components will be the class instance. So if you had `class Foo extends React.Component<{}> {}` + * and used `React.ElementRef` then the type would be the instance of `Foo`. + * - React stateless functional components do not have a backing instance and so `React.ElementRef` + * (when `Bar` is `function Bar() {}`) will give you the `undefined` type. + * - JSX intrinsics like `div` will give you their DOM instance. For `React.ElementRef<'div'>` that would be + * `HTMLDivElement`. For `React.ElementRef<'input'>` that would be `HTMLInputElement`. + * - React stateless functional components that forward a `ref` will give you the `ElementRef` of the forwarded + * to component. + * + * `C` must be the type _of_ a React component so you need to use typeof as in React.ElementRef. + * + * @todo In Flow, this works a little different with forwarded refs and the `AbstractComponent` that + * `React.forwardRef()` returns. + */ + type ElementRef< + C extends + | ForwardRefExoticComponent + | { new (props: any): Component } + | ((props: any, context?: any) => ReactElement | null) + | keyof JSX.IntrinsicElements + > = C extends ForwardRefExoticComponent + ? FP extends RefAttributes + ? FC + : never + : C extends { new (props: any): Component } + ? InstanceType + : C extends ((props: any, context?: any) => ReactElement | null) + ? undefined + : C extends keyof JSX.IntrinsicElements + ? JSX.IntrinsicElements[C] extends DOMAttributes + ? E + : never + : never; type ComponentState = any; + type Key = string | number; + /** * @internal You shouldn't need to use this type since you never see these attributes * inside your component or have to validate them. diff --git a/types/react/test/index.ts b/types/react/test/index.ts index 87aeb5494f0476..b74bb5f98b1819 100644 --- a/types/react/test/index.ts +++ b/types/react/test/index.ts @@ -440,6 +440,18 @@ function RefCarryingComponent() { ); } +const MemoizedForwardingRefComponent = React.memo(ForwardingRefComponent); +const LazyComponent = React.lazy(() => Promise.resolve({ default: RefComponent })); + +type ClassComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type FunctionComponentWithoutPropsAsRef = React.ElementRef; // $ExpectType undefined +type FunctionComponentWithPropsAsRef = React.ElementRef; // $ExpectType undefined +type HTMLIntrinsicAsRef = React.ElementRef<'div'>; // $ExpectType HTMLDivElement +type SVGIntrinsicAsRef = React.ElementRef<'svg'>; // $ExpectType SVGSVGElement +type ForwardingRefComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type MemoizedForwardingRefComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type LazyComponentAsRef = React.ElementRef; // $ExpectType RefComponent + // // Attributes // -------------------------------------------------------------------------- @@ -542,8 +554,11 @@ const mappedChildrenArray2 = React.Children.map(numberChildren, num => num); const mappedChildrenArray3 = React.Children.map(elementChildren, element => element); // $ExpectType (string | Element)[] const mappedChildrenArray4 = React.Children.map(mixedChildren, elementOrString => elementOrString); -// $ExpectType Key[] +// This test uses a conditional type because otherwise it gets flaky and can resolve to either Key or ReactText, both +// of which are aliases for `string | number`. const mappedChildrenArray5 = React.Children.map(singlePluralChildren, element => element.key); +// $ExpectType true +type mappedChildrenArray5Type = typeof mappedChildrenArray5 extends React.Key[] ? true : false; // $ExpectType string[] const mappedChildrenArray6 = React.Children.map(renderPropsChildren, element => element.name); // The return type may not be an array diff --git a/types/recharts/index.d.ts b/types/recharts/index.d.ts index b83b2f313d8a4f..5ceadd0b80762c 100644 --- a/types/recharts/index.d.ts +++ b/types/recharts/index.d.ts @@ -62,9 +62,9 @@ export type ReferenceLinePosition = 'start' | 'middle' | 'end'; export type PickedCSSStyleDeclarationKeys = 'alignmentBaseline' | 'baselineShift' | 'clip' | 'clipPath' | 'clipRule' | 'color' | 'colorInterpolationFilters' | 'cursor' | 'direction' | 'display' | 'dominantBaseline' | - 'enableBackground' | 'fill' | 'fillRule' | 'filter' | 'floodColor' | + 'fill' | 'fillRule' | 'filter' | 'floodColor' | 'floodOpacity' | 'font' | 'fontFamily' | 'fontStretch' | 'fontStyle' | 'fontVariant' | - 'glyphOrientationHorizontal' | 'glyphOrientationVertical' | 'letterSpacing' | 'lightingColor' | + 'glyphOrientationVertical' | 'letterSpacing' | 'lightingColor' | 'markerEnd' | 'markerMid' | 'markerStart' | 'mask' | 'overflow' | 'pointerEvents' | 'stopColor' | 'strokeDasharray' | 'strokeLinecap' | 'strokeLinejoin' | 'textAnchor' | 'textDecoration' | 'unicodeBidi' | 'visibility' | 'writingMode' | 'transform';