Skip to content

Commit

Permalink
Flow-ify ReactPartialRenderer (facebook#11251)
Browse files Browse the repository at this point in the history
* flow ReactPartialRenderer

* prettier

* moving flow type around;

* Move anys to DEV-only code path and keep it typechecked

* Increase Flow coverage
  • Loading branch information
iamdustan authored and Ethan-Arrowood committed Dec 8, 2017
1 parent f803707 commit 21d8689
Showing 1 changed file with 96 additions and 40 deletions.
136 changes: 96 additions & 40 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

import type {ReactElement} from 'shared/ReactElementType';

var React = require('react');
var emptyFunction = require('fbjs/lib/emptyFunction');
var emptyObject = require('fbjs/lib/emptyObject');
Expand All @@ -27,7 +31,12 @@ var escapeTextContentForBrowser = require('../shared/escapeTextContentForBrowser
var isCustomComponent = require('../shared/isCustomComponent');
var omittedCloseTags = require('../shared/omittedCloseTags');

var toArray = React.Children.toArray;
// Based on reading the React.Children implementation. TODO: type this somewhere?
type ReactNode = string | number | ReactElement;
type FlatReactChildren = Array<null | ReactNode>;
type toArrayType = (children: mixed) => FlatReactChildren;
var toArray = ((React.Children.toArray: any): toArrayType);

var getStackAddendum = emptyFunction.thatReturns('');

if (__DEV__) {
Expand Down Expand Up @@ -63,14 +72,15 @@ if (__DEV__) {
var {ReactDebugCurrentFrame} = require('shared/ReactGlobalSharedState');
var currentDebugStack = null;
var currentDebugElementStack = null;
var setCurrentDebugStack = function(stack) {
currentDebugElementStack = stack[stack.length - 1].debugElementStack;
var setCurrentDebugStack = function(stack: Array<Frame>) {
var frame: Frame = stack[stack.length - 1];
currentDebugElementStack = ((frame: any): FrameDev).debugElementStack;
// We are about to enter a new composite stack, reset the array.
currentDebugElementStack.length = 0;
currentDebugStack = stack;
ReactDebugCurrentFrame.getCurrentStack = getStackAddendum;
};
var pushElementToDebugStack = function(element) {
var pushElementToDebugStack = function(element: ReactElement) {
if (currentDebugElementStack !== null) {
currentDebugElementStack.push(element);
}
Expand All @@ -87,7 +97,8 @@ if (__DEV__) {
let stack = '';
let debugStack = currentDebugStack;
for (let i = debugStack.length - 1; i >= 0; i--) {
let debugElementStack = debugStack[i].debugElementStack;
const frame: Frame = debugStack[i];
let debugElementStack = ((frame: any): FrameDev).debugElementStack;
for (let ii = debugElementStack.length - 1; ii >= 0; ii--) {
stack += describeStackFrame(debugElementStack[ii]);
}
Expand Down Expand Up @@ -130,7 +141,7 @@ var processStyleName = memoizeStringOnly(function(styleName) {
return hyphenateStyleName(styleName);
});

function createMarkupForStyles(styles) {
function createMarkupForStyles(styles): string | null {
var serialized = '';
var delimiter = '';
for (var styleName in styles) {
Expand Down Expand Up @@ -159,7 +170,7 @@ function createMarkupForStyles(styles) {
}

function warnNoop(
publicInstance: ReactComponent<any, any, any>,
publicInstance: React$Component<any, any>,
callerName: string,
) {
if (__DEV__) {
Expand Down Expand Up @@ -195,7 +206,7 @@ function getNonChildrenInnerMarkup(props) {
return null;
}

function flattenOptionChildren(children) {
function flattenOptionChildren(children: mixed): string {
var content = '';
// Flatten children and warn if they aren't strings or numbers;
// invalid types are ignored.
Expand Down Expand Up @@ -257,13 +268,13 @@ var RESERVED_PROPS = {
};

function createOpenTagMarkup(
tagVerbatim,
tagLowercase,
props,
namespace,
makeStaticMarkup,
isRootElement,
) {
tagVerbatim: string,
tagLowercase: string,
props: Object,
namespace: string,
makeStaticMarkup: boolean,
isRootElement: boolean,
): string {
var ret = '<' + tagVerbatim;

for (var propKey in props) {
Expand Down Expand Up @@ -317,12 +328,20 @@ function validateRenderResult(child, type) {
}
}

function resolve(child, context) {
function resolve(
child: mixed,
context: Object,
): {|
child: mixed,
context: Object,
|} {
while (React.isValidElement(child)) {
// Safe because we just checked it's an element.
var element: ReactElement = ((child: any): ReactElement);
if (__DEV__) {
pushElementToDebugStack(child);
pushElementToDebugStack(element);
}
var Component = child.type;
var Component = element.type;
if (typeof Component !== 'function') {
break;
}
Expand Down Expand Up @@ -354,17 +373,17 @@ function resolve(child, context) {
};

if (shouldConstruct(Component)) {
inst = new Component(child.props, publicContext, updater);
inst = new Component(element.props, publicContext, updater);
} else {
inst = Component(child.props, publicContext, updater);
inst = Component(element.props, publicContext, updater);
if (inst == null || inst.render == null) {
child = inst;
validateRenderResult(child, Component);
continue;
}
}

inst.props = child.props;
inst.props = element.props;
inst.context = publicContext;
inst.updater = updater;

Expand All @@ -388,7 +407,7 @@ function resolve(child, context) {
for (var i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
var partial = oldQueue[i];
var partialState = typeof partial === 'function'
? partial.call(inst, nextState, child.props, publicContext)
? partial.call(inst, nextState, element.props, publicContext)
: partial;
if (partialState) {
if (dontMutate) {
Expand Down Expand Up @@ -442,20 +461,46 @@ function resolve(child, context) {
return {child, context};
}

type Frame = {
domNamespace: string,
children: FlatReactChildren,
childIndex: number,
context: Object,
footer: string,
};

type FrameDev = Frame & {
debugElementStack: Array<ReactElement>,
};

class ReactDOMServerRenderer {
constructor(element, makeStaticMarkup) {
var children = React.isValidElement(element) ? [element] : toArray(element);
var topFrame = {
stack: Array<Frame>;
exhausted: boolean;
// TODO: type this more strictly:
currentSelectValue: any;
previousWasTextNode: boolean;
makeStaticMarkup: boolean;

constructor(children: mixed, makeStaticMarkup: boolean) {
var flatChildren;
if (React.isValidElement(children)) {
// Safe because we just checked it's an element.
var element = ((children: any): ReactElement);
flatChildren = [element];
} else {
flatChildren = toArray(children);
}
var topFrame: Frame = {
// Assume all trees start in the HTML namespace (not totally true, but
// this is what we did historically)
domNamespace: Namespaces.html,
children,
children: flatChildren,
childIndex: 0,
context: emptyObject,
footer: '',
};
if (__DEV__) {
topFrame.debugElementStack = [];
((topFrame: any): FrameDev).debugElementStack = [];
}
this.stack = [topFrame];
this.exhausted = false;
Expand All @@ -464,7 +509,7 @@ class ReactDOMServerRenderer {
this.makeStaticMarkup = makeStaticMarkup;
}

read(bytes) {
read(bytes: number): string | null {
if (this.exhausted) {
return null;
}
Expand All @@ -475,7 +520,7 @@ class ReactDOMServerRenderer {
this.exhausted = true;
break;
}
var frame = this.stack[this.stack.length - 1];
var frame: Frame = this.stack[this.stack.length - 1];
if (frame.childIndex >= frame.children.length) {
var footer = frame.footer;
out += footer;
Expand All @@ -501,7 +546,11 @@ class ReactDOMServerRenderer {
return out;
}

render(child, context, parentNamespace) {
render(
child: ReactNode | null,
context: Object,
parentNamespace: string,
): string {
if (typeof child === 'string' || typeof child === 'number') {
var text = '' + child;
if (text === '') {
Expand All @@ -516,23 +565,26 @@ class ReactDOMServerRenderer {
this.previousWasTextNode = true;
return escapeTextContentForBrowser(text);
} else {
({child, context} = resolve(child, context));
if (child === null || child === false) {
var nextChild;
({child: nextChild, context} = resolve(child, context));
if (nextChild === null || nextChild === false) {
return '';
} else {
if (React.isValidElement(child)) {
return this.renderDOM(child, context, parentNamespace);
if (React.isValidElement(nextChild)) {
// Safe because we just checked it's an element.
var nextElement = ((nextChild: any): ReactElement);
return this.renderDOM(nextElement, context, parentNamespace);
} else {
var children = toArray(child);
var frame = {
var nextChildren = toArray(nextChild);
var frame: Frame = {
domNamespace: parentNamespace,
children,
children: nextChildren,
childIndex: 0,
context: context,
footer: '',
};
if (__DEV__) {
frame.debugElementStack = [];
((frame: any): FrameDev).debugElementStack = [];
}
this.stack.push(frame);
return '';
Expand All @@ -541,7 +593,11 @@ class ReactDOMServerRenderer {
}
}

renderDOM(element, context, parentNamespace) {
renderDOM(
element: ReactElement,
context: Object,
parentNamespace: string,
): string {
var tag = element.type.toLowerCase();

let namespace = parentNamespace;
Expand Down Expand Up @@ -828,7 +884,7 @@ class ReactDOMServerRenderer {
footer: footer,
};
if (__DEV__) {
frame.debugElementStack = [];
((frame: any): FrameDev).debugElementStack = [];
}
this.stack.push(frame);
this.previousWasTextNode = false;
Expand Down

0 comments on commit 21d8689

Please sign in to comment.