From 1a67b639ceae21a6e1cc0a9d7835da18839c4d68 Mon Sep 17 00:00:00 2001 From: Vincent Siao Date: Sun, 13 Mar 2016 23:17:00 -0700 Subject: [PATCH] [react] Add ComponentElement for instance type inference --- react/react-addons-test-utils.d.ts | 21 ++++---- react/react-dom.d.ts | 31 +++++++---- react/react-global-tests.ts | 8 +-- react/react-tests.ts | 42 ++++++++++----- react/react.d.ts | 82 ++++++++++++++++++++++-------- 5 files changed, 125 insertions(+), 59 deletions(-) diff --git a/react/react-addons-test-utils.d.ts b/react/react-addons-test-utils.d.ts index efe8392a1..35df25102 100644 --- a/react/react-addons-test-utils.d.ts +++ b/react/react-addons-test-utils.d.ts @@ -96,10 +96,12 @@ declare namespace __React { export function renderIntoDocument( element: DOMElement): T; + export function renderIntoDocument( + element: SFCElement): void; + export function renderIntoDocument>( + element: CElement): T; export function renderIntoDocument

( - element: ReactElement

): Component; - export function renderIntoDocument>( - element: ReactElement): C; + element: ReactElement

): Component | Element | void; export function mockComponent( mocked: MockedComponentClass, mockTagName?: string): typeof TestUtils; @@ -115,9 +117,8 @@ declare namespace __React { export function isDOMComponent(instance: ReactInstance): instance is Element; export function isCompositeComponent(instance: ReactInstance): instance is Component; - export function isCompositeComponentWithType( - instance: ReactInstance, - type: ComponentClass): boolean; + export function isCompositeComponentWithType, C extends ComponentClass>( + instance: ReactInstance, type: ClassType): T; export function findAllInRenderedTree( root: Component, @@ -137,13 +138,13 @@ declare namespace __React { root: Component, tagName: string): Element; - export function scryRenderedComponentsWithType>( + export function scryRenderedComponentsWithType, C extends ComponentClass<{}>>( root: Component, - type: { new(): T }): T[]; + type: ClassType): T[]; - export function findRenderedComponentWithType>( + export function findRenderedComponentWithType, C extends ComponentClass<{}>>( root: Component, - type: { new(): T }): T; + type: ClassType): T; export function createRenderer(): ShallowRenderer; } diff --git a/react/react-dom.d.ts b/react/react-dom.d.ts index b4154cdc9..492b546d1 100644 --- a/react/react-dom.d.ts +++ b/react/react-dom.d.ts @@ -14,14 +14,18 @@ declare namespace __React { element: DOMElement, container: Element, callback?: (element: T) => any): T; - function render( - element: ClassicElement

, + function render

( + element: SFCElement

, container: Element, - callback?: (component: ClassicComponent) => any): ClassicComponent; - function render( + callback?: () => any): void; + function render>( + element: CElement, + container: Element, + callback?: (component: T) => any): T; + function render

( element: ReactElement

, container: Element, - callback?: (component: Component) => any): Component; + callback?: (component?: Component | Element) => any): Component | Element | void; function unmountComponentAtNode(container: Element): boolean; @@ -36,16 +40,21 @@ declare namespace __React { element: DOMElement, container: Element, callback?: (element: T) => any): T; - function unstable_renderSubtreeIntoContainer( + function unstable_renderSubtreeIntoContainer>( parentComponent: Component, - nextElement: ClassicElement

, + element: CElement, container: Element, - callback?: (component: ClassicComponent) => any): ClassicComponent; - function unstable_renderSubtreeIntoContainer( + callback?: (component: T) => any): T; + function render

( parentComponent: Component, - nextElement: ReactElement

, + element: SFCElement

, container: Element, - callback?: (component: Component) => any): Component; + callback?: () => any): void; + function unstable_renderSubtreeIntoContainer

( + parentComponent: Component, + element: ReactElement

, + container: Element, + callback?: (component?: Component | Element) => any): Component | Element | void; } namespace __DOMServer { diff --git a/react/react-global-tests.ts b/react/react-global-tests.ts index b943344a5..3aaf4b2c1 100644 --- a/react/react-global-tests.ts +++ b/react/react-global-tests.ts @@ -115,9 +115,9 @@ class ModernComponent extends React.Component } // React.createFactory -var factory: React.Factory = +var factory: React.CFactory = React.createFactory(ModernComponent); -var factoryElement: React.ReactElement = +var factoryElement: React.CElement = factory(props); var classicFactory: React.ClassicFactory = @@ -131,7 +131,7 @@ var domFactoryElement: React.DOMElement = domFactory(); // React.createElement -var element: React.ReactElement = +var element: React.CElement = React.createElement(ModernComponent, props); var classicElement: React.ClassicElement = React.createElement(ClassicComponent, props); @@ -139,7 +139,7 @@ var domElement: React.ReactHTMLElement = React.createElement("div"); // React.cloneElement -var clonedElement: React.ReactElement = +var clonedElement: React.CElement = React.cloneElement(element, props); var clonedClassicElement: React.ClassicElement = React.cloneElement(classicElement, props); diff --git a/react/react-tests.ts b/react/react-tests.ts index aa485b2ae..e0d9bb7be 100644 --- a/react/react-tests.ts +++ b/react/react-tests.ts @@ -142,27 +142,31 @@ interface SCProps { foo?: number; } -var StatelessComponent = (props: SCProps) => { +function StatelessComponent(props: SCProps) { return React.DOM.div(null, props.foo); }; +namespace StatelessComponent { + export var displayName = "StatelessComponent"; + export var defaultProps = { foo: 42 }; +} -// Must explicitly type-annotate to add displayName/defaultProps/contextTypes -var StatelessComponent2: React.StatelessComponent = - (props: SCProps) => React.DOM.div(null, props.foo); +var StatelessComponent2: React.SFC = + // props is contextually typed + props => React.DOM.div(null, props.foo); StatelessComponent2.displayName = "StatelessComponent2"; StatelessComponent2.defaultProps = { foo: 42 }; // React.createFactory -var factory: React.Factory = +var factory: React.CFactory = React.createFactory(ModernComponent); -var factoryElement: React.ReactElement = +var factoryElement: React.CElement = factory(props); -var statelessFactory: React.Factory = +var statelessFactory: React.SFCFactory = React.createFactory(StatelessComponent); -var statelessElement: React.ReactElement = +var statelessElement: React.SFCElement = statelessFactory(props); var classicFactory: React.ClassicFactory = @@ -176,9 +180,9 @@ var domFactoryElement: React.DOMElement = domFactory(); // React.createElement -var element: React.ReactElement = +var element: React.CElement = React.createElement(ModernComponent, props); -var statelessElement: React.ReactElement = +var statelessElement: React.SFCElement = React.createElement(StatelessComponent, props); var classicElement: React.ClassicElement = React.createElement(ClassicComponent, props); @@ -186,9 +190,19 @@ var domElement: React.ReactHTMLElement = React.createElement("div"); // React.cloneElement -var clonedElement: React.ReactElement = +var clonedElement: React.CElement = React.cloneElement(element, { foo: 43 }); -var clonedStatelessElement: React.ReactElement = +var clonedElement2: React.CElement = + // known problem: cloning with key or ref requires cast + React.cloneElement(element, >{ + ref: c => c.reset() + }); +var clonedElement3: React.CElement = + React.cloneElement(element, <{ foo: number } & React.Attributes>{ + key: "8eac7", + foo: 55 + }); +var clonedStatelessElement: React.SFCElement = // known problem: cloning with optional props don't work properly // workaround: cast to actual props type React.cloneElement(statelessElement, { foo: 44 }); @@ -200,7 +214,7 @@ var clonedDOMElement: React.ReactHTMLElement = }); // React.render -var component: React.Component = +var component: ModernComponent = ReactDOM.render(element, container); var classicComponent: React.ClassicComponent = ReactDOM.render(classicElement, container); @@ -224,7 +238,7 @@ var type: React.ComponentClass = element.type; var elementProps: Props = element.props; var key: React.Key = element.key; -var t: React.ReactType; +var t: React.ReactType; var name = typeof t === "string" ? t : t.displayName; // diff --git a/react/react.d.ts b/react/react.d.ts index c06081019..45b80f729 100644 --- a/react/react.d.ts +++ b/react/react.d.ts @@ -10,6 +10,7 @@ declare namespace __React { // ---------------------------------------------------------------------- type ReactType = string | ComponentClass | StatelessComponent; + type Key = string | number; type Ref = string | ((instance: T) => any); @@ -20,17 +21,24 @@ declare namespace __React { ref?: Ref; } - interface ReactElement

> { - type: string | ComponentClass

| StatelessComponent

; + interface ReactElement

{ + type: string | ComponentClass

| SFC

; props: P; key?: Key; } - interface ClassicElement

extends ReactElement

{ - type: ClassicComponentClass

; - ref: Ref>; + interface SFCElement

extends ReactElement

{ + type: SFC

; } + type CElement> = ComponentElement; + interface ComponentElement> extends ReactElement

{ + type: ComponentClass

; + ref?: Ref; + } + + type ClassicElement

= CElement>; + interface DOMElement

extends ReactElement

{ type: string; ref: Ref; @@ -50,10 +58,17 @@ declare namespace __React { (props?: P & Attributes, ...children: ReactNode[]): ReactElement

; } - interface ClassicFactory

extends Factory

{ - (props?: P & ClassAttributes>, ...children: ReactNode[]): ClassicElement

; + interface SFCFactory

{ + (props?: P & Attributes, ...children: ReactNode[]): SFCElement

; } + interface ComponentFactory> { + (props?: P & ClassAttributes, ...children: ReactNode[]): CElement; + } + + type CFactory> = ComponentFactory; + type ClassicFactory

= CFactory>; + interface DOMFactory

{ (props?: P & ClassAttributes, ...children: ReactNode[]): DOMElement; } @@ -84,20 +99,32 @@ declare namespace __React { function createFactory

( type: string): DOMFactory; - function createFactory

(type: ClassicComponentClass

): ClassicFactory

; - function createFactory

(type: ComponentClass

| StatelessComponent

): Factory

; + function createFactory

(type: SFC

): SFCFactory

; + function createFactory

( + type: ClassType, ClassicComponentClass

>): CFactory>; + function createFactory, C extends ComponentClass

>( + type: ClassType): CFactory; + function createFactory

(type: ComponentClass

| SFC

): Factory

; function createElement

( type: string, props?: P & ClassAttributes, ...children: ReactNode[]): DOMElement; function createElement

( - type: ClassicComponentClass

, - props?: P & ClassAttributes>, - ...children: ReactNode[]): ClassicElement

; + type: SFC

, + props?: P & Attributes, + ...children: ReactNode[]): SFCElement

; function createElement

( - type: ComponentClass

| StatelessComponent

, - props?: P & ClassAttributes>, + type: ClassType, ClassicComponentClass

>, + props?: P & ClassAttributes>, + ...children: ReactNode[]): CElement>; + function createElement, C extends ComponentClass

>( + type: ClassType, + props?: P & ClassAttributes, + ...children: ReactNode[]): CElement; + function createElement

( + type: ComponentClass

| SFC

, + props?: P & Attributes, ...children: ReactNode[]): ReactElement

; function cloneElement

( @@ -105,12 +132,16 @@ declare namespace __React { props?: P & ClassAttributes, ...children: ReactNode[]): DOMElement; function cloneElement

( - element: ClassicElement

, - props?: Q & ClassAttributes>, - ...children: ReactNode[]): ClassicElement

; + element: SFCElement

, + props?: Q, // should be Q & Attributes, but then Q is inferred as {} + ...children: ReactNode[]): SFCElement

; + function cloneElement

>( + element: CElement, + props?: Q, // should be Q & ClassAttributes + ...children: ReactNode[]): CElement; function cloneElement

( element: ReactElement

, - props?: Q & Attributes, + props?: Q, // should be Q & Attributes ...children: ReactNode[]): ReactElement

; function isValidElement

(object: {}): object is ReactElement

; @@ -160,6 +191,7 @@ declare namespace __React { // Class Interfaces // ---------------------------------------------------------------------- + type SFC

= StatelessComponent

; interface StatelessComponent

{ (props?: P, context?: any): ReactElement; propTypes?: ValidationMap

; @@ -169,7 +201,7 @@ declare namespace __React { } interface ComponentClass

{ - new(props?: P, context?: any): Component; + new(props?: P, context?: any): Component; propTypes?: ValidationMap

; contextTypes?: ValidationMap; childContextTypes?: ValidationMap; @@ -178,10 +210,20 @@ declare namespace __React { } interface ClassicComponentClass

extends ComponentClass

{ - new(props?: P, context?: any): ClassicComponent; + new(props?: P, context?: any): ClassicComponent; getDefaultProps?(): P; } + /** + * We use an intersection type to infer multiple type parameters from + * a single argument, which is useful for many top-level API defs. + * See https://github.com/Microsoft/TypeScript/issues/7234 for more info. + */ + type ClassType, C extends ComponentClass

> = + C & + (new() => T) & + (new() => { props: P }); + // // Component Specs and Lifecycle // ----------------------------------------------------------------------