mirror of
https://github.com/facebook/react.git
synced 2026-02-24 12:43:00 +00:00
[Fiber] Compute the Host Diff During Reconciliation (#8607)
* Allow renderers to return an update payload in prepareUpdate This then gets stored on updateQueue so that the renderer doesn't need to think about how to store this. It then gets passed into commitUpdate during the commit phase. This allows renderers to do the diffing during the time sliced path, allocate a queue for changes and do only the absolute minimal work to apply those changes in the commit phase. If refs update we still schedule and update. * Hack around the listener problem * Diff ReactDOMFiber properties in prepareUpdate We now take advantage of the new capability to diff properties early. We do this by generating an update payload in the form of an array with each property name and value that we're about to update. * Add todo for handling wasCustomComponentTag * Always force an update to wrapper components Wrapper components have custom logic that gets applied at the commit phase so we always need to ensure that we schedule an update for them. * Remove rootContainerInstance from commitMount No use case yet and I removed it from commitUpdate earlier. * Use update signal object in test renderer * Incorporate 8652 into new algorithm * Fix comment * Add failing test for flipping event handlers This illustrates the problem that happens if we store a pointer to the Fiber and then choose not to update that pointer when no properties change. That causes an old Fiber to be retained on the DOM node. Then that Fiber can be reused by the pooling mechanism which then will mutate that Fiber with new event handlers, which makes them active before commit. * Store current props in the RN instance cache and on the DOM node This represents the current set of event listeners. By not relying on the Fiber, it allows us to avoid doing any effects in the commit phase when nothing changes. This is a bit ugly. Not super happy how this all came together.
This commit is contained in:
committed by
GitHub
parent
c6a7dc7f24
commit
b354db22a9
@@ -37,6 +37,9 @@ src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js
|
||||
* should throw on full document render w/ no markup
|
||||
* supports findDOMNode on full-page components
|
||||
|
||||
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
|
||||
* should control a value in reentrant events
|
||||
|
||||
src/renderers/shared/__tests__/ReactPerf-test.js
|
||||
* should count no-op update as waste
|
||||
* should count no-op update in child as waste
|
||||
|
||||
@@ -546,6 +546,7 @@ src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
|
||||
* findDOMNode should find dom element after expanding a fragment
|
||||
* should bubble events from the portal to the parent
|
||||
* should not onMouseLeave when staying in the portal
|
||||
* should not update event handlers until commit
|
||||
* should not crash encountering low-priority tree
|
||||
* throws if non-element passed to top-level render
|
||||
* throws if something other than false, null, or an element is returned from render
|
||||
@@ -959,7 +960,6 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMIframe-test.js
|
||||
|
||||
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
|
||||
* should properly control a value even if no event listener exists
|
||||
* should control a value in reentrant events
|
||||
* should control values in reentrant events with different targets
|
||||
* should display `defaultValue` of number 0
|
||||
* only assigns defaultValue if it changes
|
||||
|
||||
@@ -42,6 +42,8 @@ const TYPES = {
|
||||
TEXT: 'Text',
|
||||
};
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
/** Helper Methods */
|
||||
|
||||
function addEventListeners(instance, type, listener) {
|
||||
@@ -418,7 +420,7 @@ const ARTRenderer = ReactFiberReconciler({
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(instance, type, oldProps, newProps) {
|
||||
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
|
||||
instance._applyProps(instance, newProps, oldProps);
|
||||
},
|
||||
|
||||
@@ -482,7 +484,7 @@ const ARTRenderer = ReactFiberReconciler({
|
||||
},
|
||||
|
||||
prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
return true;
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
removeChild(parentInstance, child) {
|
||||
|
||||
@@ -35,9 +35,13 @@ var {
|
||||
createElement,
|
||||
getChildNamespace,
|
||||
setInitialProperties,
|
||||
diffProperties,
|
||||
updateProperties,
|
||||
} = ReactDOMFiberComponent;
|
||||
var { precacheFiberNode } = ReactDOMComponentTree;
|
||||
var {
|
||||
precacheFiberNode,
|
||||
updateFiberEventHandlers,
|
||||
} = ReactDOMComponentTree;
|
||||
|
||||
if (__DEV__) {
|
||||
var validateDOMNesting = require('validateDOMNesting');
|
||||
@@ -184,6 +188,7 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
}
|
||||
const domElement : Instance = createElement(type, props, rootContainerInstance, parentNamespace);
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateFiberEventHandlers(domElement, props);
|
||||
return domElement;
|
||||
},
|
||||
|
||||
@@ -206,8 +211,9 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : HostContext,
|
||||
) : boolean {
|
||||
) : null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext : any) : HostContextDev);
|
||||
if (typeof newProps.children !== typeof oldProps.children && (
|
||||
@@ -218,14 +224,13 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
validateDOMNesting(null, String(newProps.children), null, ownAncestorInfo);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return diffProperties(domElement, type, oldProps, newProps, rootContainerInstance);
|
||||
},
|
||||
|
||||
commitMount(
|
||||
domElement : Instance,
|
||||
type : string,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
internalInstanceHandle : Object,
|
||||
) : void {
|
||||
((domElement : any) : HTMLButtonElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).focus();
|
||||
@@ -233,16 +238,17 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
|
||||
commitUpdate(
|
||||
domElement : Instance,
|
||||
updatePayload : Array<mixed>,
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
internalInstanceHandle : Object,
|
||||
) : void {
|
||||
// Update the internal instance handle so that we know which props are
|
||||
// the current ones.
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateProperties(domElement, type, oldProps, newProps, rootContainerInstance);
|
||||
// Update the props handle so that we know which props are the ones with
|
||||
// with current event handlers.
|
||||
updateFiberEventHandlers(domElement, newProps);
|
||||
// Apply the diff to the DOM node.
|
||||
updateProperties(domElement, updatePayload, type, oldProps, newProps);
|
||||
},
|
||||
|
||||
shouldSetTextContent(props : Props) : boolean {
|
||||
|
||||
@@ -301,69 +301,15 @@ function isCustomComponent(tagName, props) {
|
||||
return tagName.indexOf('-') >= 0 || props.is != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciles the properties by detecting differences in property values and
|
||||
* updating the DOM as necessary. This function is probably the single most
|
||||
* critical path for performance optimization.
|
||||
*
|
||||
* TODO: Benchmark whether checking for changed values in memory actually
|
||||
* improves performance (especially statically positioned elements).
|
||||
* TODO: Benchmark the effects of putting this at the top since 99% of props
|
||||
* do not change for a given reconciliation.
|
||||
* TODO: Benchmark areas that can be improved with caching.
|
||||
*/
|
||||
function updateDOMProperties(
|
||||
function setInitialDOMProperties(
|
||||
domElement : Element,
|
||||
rootContainerElement : Element,
|
||||
lastProps : null | Object,
|
||||
nextProps : Object,
|
||||
wasCustomComponentTag : boolean,
|
||||
isCustomComponentTag : boolean,
|
||||
) : void {
|
||||
var propKey;
|
||||
var styleName;
|
||||
var styleUpdates;
|
||||
for (propKey in lastProps) {
|
||||
if (nextProps.hasOwnProperty(propKey) ||
|
||||
!lastProps.hasOwnProperty(propKey) ||
|
||||
lastProps[propKey] == null) {
|
||||
continue;
|
||||
}
|
||||
if (propKey === STYLE) {
|
||||
var lastStyle = lastProps[propKey];
|
||||
for (styleName in lastStyle) {
|
||||
if (lastStyle.hasOwnProperty(styleName)) {
|
||||
styleUpdates = styleUpdates || {};
|
||||
styleUpdates[styleName] = '';
|
||||
}
|
||||
}
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
|
||||
propKey === CHILDREN) {
|
||||
// TODO: Clear innerHTML. This is currently broken in Fiber because we are
|
||||
// too late to clear everything at this point because new children have
|
||||
// already been inserted.
|
||||
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
|
||||
// Noop
|
||||
} else if (registrationNameModules.hasOwnProperty(propKey)) {
|
||||
// Do nothing for deleted listeners.
|
||||
} else if (wasCustomComponentTag) {
|
||||
DOMPropertyOperations.deleteValueForAttribute(
|
||||
domElement,
|
||||
propKey
|
||||
);
|
||||
} else if (
|
||||
DOMProperty.properties[propKey] ||
|
||||
DOMProperty.isCustomAttribute(propKey)) {
|
||||
DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
|
||||
}
|
||||
}
|
||||
for (propKey in nextProps) {
|
||||
for (var propKey in nextProps) {
|
||||
var nextProp = nextProps[propKey];
|
||||
var lastProp =
|
||||
lastProps != null ? lastProps[propKey] : undefined;
|
||||
if (!nextProps.hasOwnProperty(propKey) ||
|
||||
nextProp === lastProp ||
|
||||
nextProp == null && lastProp == null) {
|
||||
if (!nextProps.hasOwnProperty(propKey)) {
|
||||
continue;
|
||||
}
|
||||
if (propKey === STYLE) {
|
||||
@@ -374,38 +320,16 @@ function updateDOMProperties(
|
||||
Object.freeze(nextProp);
|
||||
}
|
||||
}
|
||||
if (lastProp) {
|
||||
// Unset styles on `lastProp` but not on `nextProp`.
|
||||
for (styleName in lastProp) {
|
||||
if (lastProp.hasOwnProperty(styleName) &&
|
||||
(!nextProp || !nextProp.hasOwnProperty(styleName))) {
|
||||
styleUpdates = styleUpdates || {};
|
||||
styleUpdates[styleName] = '';
|
||||
}
|
||||
}
|
||||
// Update styles that changed since `lastProp`.
|
||||
for (styleName in nextProp) {
|
||||
if (nextProp.hasOwnProperty(styleName) &&
|
||||
lastProp[styleName] !== nextProp[styleName]) {
|
||||
styleUpdates = styleUpdates || {};
|
||||
styleUpdates[styleName] = nextProp[styleName];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Relies on `updateStylesByID` not mutating `styleUpdates`.
|
||||
styleUpdates = nextProp;
|
||||
}
|
||||
// Relies on `updateStylesByID` not mutating `styleUpdates`.
|
||||
// TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
domElement,
|
||||
nextProp,
|
||||
);
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
||||
var nextHtml = nextProp ? nextProp[HTML] : undefined;
|
||||
var lastHtml = lastProp ? lastProp[HTML] : undefined;
|
||||
// Intentional use of != to avoid catching zero/false.
|
||||
if (nextHtml != null) {
|
||||
if (lastHtml !== nextHtml) {
|
||||
setInnerHTML(domElement, '' + nextHtml);
|
||||
}
|
||||
} else {
|
||||
// TODO: It might be too late to clear this if we have children
|
||||
// inserted already.
|
||||
setInnerHTML(domElement, nextHtml);
|
||||
}
|
||||
} else if (propKey === CHILDREN) {
|
||||
if (typeof nextProp === 'string') {
|
||||
@@ -433,18 +357,57 @@ function updateDOMProperties(
|
||||
// brings us in line with the same behavior we have on initial render.
|
||||
if (nextProp != null) {
|
||||
DOMPropertyOperations.setValueForProperty(domElement, propKey, nextProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDOMProperties(
|
||||
domElement : Element,
|
||||
updatePayload : Array<any>,
|
||||
wasCustomComponentTag : boolean,
|
||||
isCustomComponentTag : boolean,
|
||||
) : void {
|
||||
// TODO: Handle wasCustomComponentTag
|
||||
for (var i = 0; i < updatePayload.length; i+=2) {
|
||||
var propKey = updatePayload[i];
|
||||
var propValue = updatePayload[i + 1];
|
||||
if (propKey === STYLE) {
|
||||
// TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
domElement,
|
||||
propValue,
|
||||
);
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
||||
setInnerHTML(domElement, propValue);
|
||||
} else if (propKey === CHILDREN) {
|
||||
setTextContent(domElement, propValue);
|
||||
} else if (isCustomComponentTag) {
|
||||
if (propValue != null) {
|
||||
DOMPropertyOperations.setValueForAttribute(
|
||||
domElement,
|
||||
propKey,
|
||||
propValue
|
||||
);
|
||||
} else {
|
||||
DOMPropertyOperations.deleteValueForAttribute(
|
||||
domElement,
|
||||
propKey
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
DOMProperty.properties[propKey] ||
|
||||
DOMProperty.isCustomAttribute(propKey)) {
|
||||
// If we're updating to null or undefined, we should remove the property
|
||||
// from the DOM node instead of inadvertently setting to a string. This
|
||||
// brings us in line with the same behavior we have on initial render.
|
||||
if (propValue != null) {
|
||||
DOMPropertyOperations.setValueForProperty(domElement, propKey, propValue);
|
||||
} else {
|
||||
DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (styleUpdates) {
|
||||
// TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
domElement,
|
||||
styleUpdates,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes there is no parent namespace.
|
||||
@@ -592,12 +555,10 @@ var ReactDOMFiberComponent = {
|
||||
|
||||
assertValidProps(tag, props);
|
||||
|
||||
updateDOMProperties(
|
||||
setInitialDOMProperties(
|
||||
domElement,
|
||||
rootContainerElement,
|
||||
null,
|
||||
props,
|
||||
false,
|
||||
isCustomComponentTag
|
||||
);
|
||||
|
||||
@@ -626,35 +587,42 @@ var ReactDOMFiberComponent = {
|
||||
}
|
||||
},
|
||||
|
||||
updateProperties(
|
||||
// Calculate the diff between the two objects.
|
||||
diffProperties(
|
||||
domElement : Element,
|
||||
tag : string,
|
||||
lastRawProps : Object,
|
||||
nextRawProps : Object,
|
||||
rootContainerElement : Element,
|
||||
) : void {
|
||||
) : null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
validatePropertiesInDevelopment(tag, nextRawProps);
|
||||
}
|
||||
|
||||
var updatePayload : null | Array<any> = null;
|
||||
|
||||
var lastProps : Object;
|
||||
var nextProps : Object;
|
||||
switch (tag) {
|
||||
case 'input':
|
||||
lastProps = ReactDOMFiberInput.getHostProps(domElement, lastRawProps);
|
||||
nextProps = ReactDOMFiberInput.getHostProps(domElement, nextRawProps);
|
||||
updatePayload = [];
|
||||
break;
|
||||
case 'option':
|
||||
lastProps = ReactDOMFiberOption.getHostProps(domElement, lastRawProps);
|
||||
nextProps = ReactDOMFiberOption.getHostProps(domElement, nextRawProps);
|
||||
updatePayload = [];
|
||||
break;
|
||||
case 'select':
|
||||
lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastRawProps);
|
||||
nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextRawProps);
|
||||
updatePayload = [];
|
||||
break;
|
||||
case 'textarea':
|
||||
lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastRawProps);
|
||||
nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextRawProps);
|
||||
updatePayload = [];
|
||||
break;
|
||||
default:
|
||||
lastProps = lastRawProps;
|
||||
@@ -668,17 +636,154 @@ var ReactDOMFiberComponent = {
|
||||
}
|
||||
|
||||
assertValidProps(tag, nextProps);
|
||||
var wasCustomComponentTag = isCustomComponent(tag, lastProps);
|
||||
var isCustomComponentTag = isCustomComponent(tag, nextProps);
|
||||
|
||||
var propKey;
|
||||
var styleName;
|
||||
var styleUpdates = null;
|
||||
for (propKey in lastProps) {
|
||||
if (nextProps.hasOwnProperty(propKey) ||
|
||||
!lastProps.hasOwnProperty(propKey) ||
|
||||
lastProps[propKey] == null) {
|
||||
continue;
|
||||
}
|
||||
if (propKey === STYLE) {
|
||||
var lastStyle = lastProps[propKey];
|
||||
for (styleName in lastStyle) {
|
||||
if (lastStyle.hasOwnProperty(styleName)) {
|
||||
if (!styleUpdates) {
|
||||
styleUpdates = {};
|
||||
}
|
||||
styleUpdates[styleName] = '';
|
||||
}
|
||||
}
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
|
||||
propKey === CHILDREN) {
|
||||
// Noop. This is handled by the clear text mechanism.
|
||||
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
|
||||
// Noop
|
||||
} else if (registrationNameModules.hasOwnProperty(propKey)) {
|
||||
// This is a special case. If any listener updates we need to ensure
|
||||
// that the "current" fiber pointer gets updated so we need a commit
|
||||
// to update this element.
|
||||
if (!updatePayload) {
|
||||
updatePayload = [];
|
||||
}
|
||||
} else {
|
||||
// For all other deleted properties we add it to the queue. We use
|
||||
// the whitelist in the commit phase instead.
|
||||
(updatePayload = updatePayload || []).push(propKey, null);
|
||||
}
|
||||
}
|
||||
for (propKey in nextProps) {
|
||||
var nextProp = nextProps[propKey];
|
||||
var lastProp =
|
||||
lastProps != null ? lastProps[propKey] : undefined;
|
||||
if (!nextProps.hasOwnProperty(propKey) ||
|
||||
nextProp === lastProp ||
|
||||
nextProp == null && lastProp == null) {
|
||||
continue;
|
||||
}
|
||||
if (propKey === STYLE) {
|
||||
if (__DEV__) {
|
||||
if (nextProp) {
|
||||
// Freeze the next style object so that we can assume it won't be
|
||||
// mutated. We have already warned for this in the past.
|
||||
Object.freeze(nextProp);
|
||||
}
|
||||
}
|
||||
if (lastProp) {
|
||||
// Unset styles on `lastProp` but not on `nextProp`.
|
||||
for (styleName in lastProp) {
|
||||
if (lastProp.hasOwnProperty(styleName) &&
|
||||
(!nextProp || !nextProp.hasOwnProperty(styleName))) {
|
||||
if (!styleUpdates) {
|
||||
styleUpdates = {};
|
||||
}
|
||||
styleUpdates[styleName] = '';
|
||||
}
|
||||
}
|
||||
// Update styles that changed since `lastProp`.
|
||||
for (styleName in nextProp) {
|
||||
if (nextProp.hasOwnProperty(styleName) &&
|
||||
lastProp[styleName] !== nextProp[styleName]) {
|
||||
if (!styleUpdates) {
|
||||
styleUpdates = {};
|
||||
}
|
||||
styleUpdates[styleName] = nextProp[styleName];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Relies on `updateStylesByID` not mutating `styleUpdates`.
|
||||
if (!styleUpdates) {
|
||||
if (!updatePayload) {
|
||||
updatePayload = [];
|
||||
}
|
||||
updatePayload.push(propKey, styleUpdates);
|
||||
}
|
||||
styleUpdates = nextProp;
|
||||
}
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
||||
var nextHtml = nextProp ? nextProp[HTML] : undefined;
|
||||
var lastHtml = lastProp ? lastProp[HTML] : undefined;
|
||||
if (nextHtml != null) {
|
||||
if (lastHtml !== nextHtml) {
|
||||
(updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
|
||||
}
|
||||
} else {
|
||||
// TODO: It might be too late to clear this if we have children
|
||||
// inserted already.
|
||||
}
|
||||
} else if (propKey === CHILDREN) {
|
||||
if (lastProp !== nextProp && (
|
||||
typeof nextProp === 'string' || typeof nextProp === 'number'
|
||||
)) {
|
||||
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
|
||||
}
|
||||
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
|
||||
// Noop
|
||||
} else if (registrationNameModules.hasOwnProperty(propKey)) {
|
||||
if (nextProp) {
|
||||
// We eagerly listen to this even though we haven't committed yet.
|
||||
ensureListeningTo(rootContainerElement, propKey);
|
||||
}
|
||||
if (!updatePayload && lastProp !== nextProp) {
|
||||
// This is a special case. If any listener updates we need to ensure
|
||||
// that the "current" props pointer gets updated so we need a commit
|
||||
// to update this element.
|
||||
updatePayload = [];
|
||||
}
|
||||
} else {
|
||||
// For any other property we always add it to the queue and then we
|
||||
// filter it out using the whitelist during the commit.
|
||||
(updatePayload = updatePayload || []).push(propKey, nextProp);
|
||||
}
|
||||
}
|
||||
if (styleUpdates) {
|
||||
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
|
||||
}
|
||||
return updatePayload;
|
||||
},
|
||||
|
||||
// Apply the diff.
|
||||
updateProperties(
|
||||
domElement : Element,
|
||||
updatePayload : Array<any>,
|
||||
tag : string,
|
||||
lastRawProps : Object,
|
||||
nextRawProps : Object
|
||||
) : void {
|
||||
var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
|
||||
var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
|
||||
// Apply the diff.
|
||||
updateDOMProperties(
|
||||
domElement,
|
||||
rootContainerElement,
|
||||
lastProps,
|
||||
nextProps,
|
||||
updatePayload,
|
||||
wasCustomComponentTag,
|
||||
isCustomComponentTag
|
||||
);
|
||||
|
||||
// TODO: Ensure that an update gets scheduled if any of the special props
|
||||
// changed.
|
||||
switch (tag) {
|
||||
case 'input':
|
||||
// Update the wrapper around inputs *after* updating props. This has to
|
||||
|
||||
@@ -994,6 +994,89 @@ describe('ReactDOMFiber', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not update event handlers until commit', () => {
|
||||
let ops = [];
|
||||
const handlerA = () => ops.push('A');
|
||||
const handlerB = () => ops.push('B');
|
||||
|
||||
class Example extends React.Component {
|
||||
state = { flip: false, count: 0 };
|
||||
flip() {
|
||||
this.setState({ flip: true, count: this.state.count + 1 });
|
||||
}
|
||||
tick() {
|
||||
this.setState({ count: this.state.count + 1 });
|
||||
}
|
||||
render() {
|
||||
const useB = !this.props.forceA && this.state.flip;
|
||||
return (
|
||||
<div onClick={useB ? handlerB : handlerA} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Click extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
click(node);
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let inst;
|
||||
ReactDOM.render([<Example ref={n => inst = n} />], container);
|
||||
const node = container.firstChild;
|
||||
expect(node.tagName).toEqual('DIV');
|
||||
|
||||
function click(target) {
|
||||
var fakeNativeEvent = {};
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
target,
|
||||
fakeNativeEvent
|
||||
);
|
||||
}
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['A']);
|
||||
ops = [];
|
||||
|
||||
// Render with the other event handler.
|
||||
inst.flip();
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Rerender without changing any props.
|
||||
inst.tick();
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Render a flip back to the A handler. The second component invokes the
|
||||
// click handler during render to simulate a click during an aborted
|
||||
// render. I use this hack because at current time we don't have a way to
|
||||
// test aborted ReactDOM renders.
|
||||
ReactDOM.render([<Example forceA={true} />, <Click />], container);
|
||||
|
||||
// Because the new click handler has not yet committed, we should still
|
||||
// invoke B.
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Any click that happens after commit, should invoke A.
|
||||
click(node);
|
||||
expect(ops).toEqual(['A']);
|
||||
|
||||
});
|
||||
|
||||
it('should not crash encountering low-priority tree', () => {
|
||||
ReactDOM.render(
|
||||
<div hidden={true}>
|
||||
|
||||
@@ -20,8 +20,11 @@ var invariant = require('invariant');
|
||||
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
|
||||
var Flags = ReactDOMComponentFlags;
|
||||
|
||||
var internalInstanceKey =
|
||||
'__reactInternalInstance$' + Math.random().toString(36).slice(2);
|
||||
var randomKey = Math.random().toString(36).slice(2);
|
||||
|
||||
var internalInstanceKey = '__reactInternalInstance$' + randomKey;
|
||||
|
||||
var internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
|
||||
|
||||
/**
|
||||
* Check if a given node should be cached.
|
||||
@@ -218,6 +221,14 @@ function getNodeFromInstance(inst) {
|
||||
return inst._hostNode;
|
||||
}
|
||||
|
||||
function getFiberEventHandlersFromNode(node) {
|
||||
return node[internalEventHandlersKey] || null;
|
||||
}
|
||||
|
||||
function updateFiberEventHandlers(node, props) {
|
||||
node[internalEventHandlersKey] = props;
|
||||
}
|
||||
|
||||
var ReactDOMComponentTree = {
|
||||
getClosestInstanceFromNode: getClosestInstanceFromNode,
|
||||
getInstanceFromNode: getInstanceFromNode,
|
||||
@@ -226,6 +237,8 @@ var ReactDOMComponentTree = {
|
||||
precacheNode: precacheNode,
|
||||
uncacheNode: uncacheNode,
|
||||
precacheFiberNode: precacheFiberNode,
|
||||
getFiberEventHandlersFromNode,
|
||||
updateFiberEventHandlers,
|
||||
};
|
||||
|
||||
module.exports = ReactDOMComponentTree;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
var invariant = require('invariant');
|
||||
|
||||
var instanceCache = {};
|
||||
var instanceProps = {};
|
||||
|
||||
/**
|
||||
* Drill down (through composites and empty components) until we get a host or
|
||||
@@ -65,6 +66,14 @@ function getTagFromInstance(inst) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
function getFiberEventHandlersFromTag(tag) {
|
||||
return instanceProps[tag] || null;
|
||||
}
|
||||
|
||||
function updateFiberEventHandlers(tag, props) {
|
||||
instanceProps[tag] = props;
|
||||
}
|
||||
|
||||
var ReactNativeComponentTree = {
|
||||
getClosestInstanceFromNode: getInstanceFromTag,
|
||||
getInstanceFromNode: getInstanceFromTag,
|
||||
@@ -73,6 +82,8 @@ var ReactNativeComponentTree = {
|
||||
precacheNode,
|
||||
uncacheFiberNode,
|
||||
uncacheNode,
|
||||
getFiberEventHandlersFromNode: getFiberEventHandlersFromTag,
|
||||
updateFiberEventHandlers,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentTree;
|
||||
|
||||
@@ -33,7 +33,11 @@ const emptyObject = require('emptyObject');
|
||||
const findNodeHandle = require('findNodeHandle');
|
||||
const invariant = require('invariant');
|
||||
|
||||
const { precacheFiberNode, uncacheFiberNode } = ReactNativeComponentTree;
|
||||
const {
|
||||
precacheFiberNode,
|
||||
uncacheFiberNode,
|
||||
updateFiberEventHandlers,
|
||||
} = ReactNativeComponentTree;
|
||||
|
||||
ReactNativeInjection.inject();
|
||||
|
||||
@@ -114,7 +118,6 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
instance : Instance,
|
||||
type : string,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Object,
|
||||
internalInstanceHandle : Object
|
||||
) : void {
|
||||
// Noop
|
||||
@@ -122,15 +125,15 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
|
||||
commitUpdate(
|
||||
instance : Instance,
|
||||
updatePayloadTODO : Object,
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Object,
|
||||
internalInstanceHandle : Object
|
||||
) : void {
|
||||
const viewConfig = instance.viewConfig;
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, instance._nativeTag);
|
||||
updateFiberEventHandlers(instance._nativeTag, newProps);
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.diff(
|
||||
oldProps,
|
||||
@@ -149,7 +152,7 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
type : string,
|
||||
props : Props,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : Object,
|
||||
hostContext : {||},
|
||||
internalInstanceHandle : Object
|
||||
) : Instance {
|
||||
const tag = ReactNativeTagHandles.allocateTag();
|
||||
@@ -178,6 +181,7 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
const component = new NativeHostComponent(tag, viewConfig);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
updateFiberEventHandlers(tag, props);
|
||||
|
||||
return component;
|
||||
},
|
||||
@@ -185,7 +189,7 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
createTextInstance(
|
||||
text : string,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : Object,
|
||||
hostContext : {||},
|
||||
internalInstanceHandle : Object,
|
||||
) : TextInstance {
|
||||
const tag = ReactNativeTagHandles.allocateTag();
|
||||
@@ -224,11 +228,11 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
return false;
|
||||
},
|
||||
|
||||
getRootHostContext() {
|
||||
getRootHostContext() : {||} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
getChildHostContext() : {||} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
@@ -290,9 +294,11 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
instance : Instance,
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props
|
||||
) : boolean {
|
||||
return true;
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : {||}
|
||||
) : null | Object {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
removeChild(
|
||||
|
||||
@@ -29,6 +29,8 @@ var {
|
||||
} = require('ReactPriorityLevel');
|
||||
var emptyObject = require('emptyObject');
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
var scheduledAnimationCallback = null;
|
||||
var scheduledDeferredCallback = null;
|
||||
|
||||
@@ -78,15 +80,15 @@ var NoopRenderer = ReactFiberReconciler({
|
||||
return false;
|
||||
},
|
||||
|
||||
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : boolean {
|
||||
return true;
|
||||
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : null | {} {
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
commitMount(instance : Instance, type : string, newProps : Props) : void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : void {
|
||||
commitUpdate(instance : Instance, updatePayload : Object, type : string, oldProps : Props, newProps : Props) : void {
|
||||
instance.prop = newProps.prop;
|
||||
},
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ if (__DEV__) {
|
||||
var warnedAboutStatelessRefs = {};
|
||||
}
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX>,
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
hostContext : HostContext<C, CX>,
|
||||
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
|
||||
getPriorityContext : () => PriorityLevel,
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { HostContext } from 'ReactFiberHostContext';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
@@ -34,9 +33,8 @@ var {
|
||||
ContentReset,
|
||||
} = require('ReactTypeOfSideEffect');
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX>,
|
||||
hostContext : HostContext<C, CX>,
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
captureError : (failedFiber : Fiber, error: Error) => ?Fiber
|
||||
) {
|
||||
|
||||
@@ -51,10 +49,6 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
getPublicInstance,
|
||||
} = config;
|
||||
|
||||
const {
|
||||
getRootHostContainer,
|
||||
} = hostContext;
|
||||
|
||||
// Capture errors so they don't interrupt unmounting.
|
||||
function safelyCallComponentWillUnmount(current, instance) {
|
||||
try {
|
||||
@@ -367,9 +361,13 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
// Commit the work prepared earlier.
|
||||
const newProps = finishedWork.memoizedProps;
|
||||
const oldProps = current.memoizedProps;
|
||||
const rootContainerInstance = getRootHostContainer();
|
||||
const type = finishedWork.type;
|
||||
commitUpdate(instance, type, oldProps, newProps, rootContainerInstance, finishedWork);
|
||||
// TODO: Type the updateQueue to be specific to host components.
|
||||
const updatePayload : null | PL = (finishedWork.updateQueue : any);
|
||||
finishedWork.updateQueue = null;
|
||||
if (updatePayload) {
|
||||
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
|
||||
}
|
||||
}
|
||||
detachRefIfNeeded(current, finishedWork);
|
||||
return;
|
||||
@@ -438,8 +436,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
) {
|
||||
const type = finishedWork.type;
|
||||
const props = finishedWork.memoizedProps;
|
||||
const rootContainerInstance = getRootHostContainer();
|
||||
commitMount(instance, type, props, rootContainerInstance, finishedWork);
|
||||
commitMount(instance, type, props, finishedWork);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -47,8 +47,8 @@ if (__DEV__) {
|
||||
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
|
||||
}
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX>,
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
hostContext : HostContext<C, CX>,
|
||||
) {
|
||||
const {
|
||||
@@ -186,8 +186,9 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case HostComponent:
|
||||
case HostComponent: {
|
||||
popHostContext(workInProgress);
|
||||
const rootContainerInstance = getRootHostContainer();
|
||||
const type = workInProgress.type;
|
||||
const newProps = workInProgress.memoizedProps;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
@@ -200,8 +201,12 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
// Even better would be if children weren't special cased at all tho.
|
||||
const instance : I = workInProgress.stateNode;
|
||||
const currentHostContext = getHostContext();
|
||||
if (prepareUpdate(instance, type, oldProps, newProps, currentHostContext)) {
|
||||
// This returns true if there was something to update.
|
||||
const updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
|
||||
// TODO: Type this specific to this type of component.
|
||||
workInProgress.updateQueue = (updatePayload : any);
|
||||
// If the update payload indicates that there is a change or if there
|
||||
// is a new ref we mark this as an update.
|
||||
if (updatePayload || current.ref !== workInProgress.ref) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
} else {
|
||||
@@ -214,7 +219,6 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
}
|
||||
}
|
||||
|
||||
const rootContainerInstance = getRootHostContainer();
|
||||
const currentHostContext = getHostContext();
|
||||
// TODO: Move createInstance to beginWork and keep it on a context
|
||||
// "stack" as the parent. Then append children as we go in beginWork
|
||||
@@ -244,7 +248,8 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case HostText:
|
||||
}
|
||||
case HostText: {
|
||||
let newText = workInProgress.memoizedProps;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
const oldText = current.memoizedProps;
|
||||
@@ -268,6 +273,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
workInProgress.stateNode = textInstance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case CoroutineComponent:
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
|
||||
@@ -34,8 +34,8 @@ export type HostContext<C, CX> = {
|
||||
resetHostContainer() : void,
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX>
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config : HostConfig<T, P, I, TI, PI, C, CX, PL>
|
||||
) : HostContext<C, CX> {
|
||||
const {
|
||||
getChildHostContext,
|
||||
|
||||
@@ -43,7 +43,7 @@ export type Deadline = {
|
||||
|
||||
type OpaqueNode = Fiber;
|
||||
|
||||
export type HostConfig<T, P, I, TI, PI, C, CX> = {
|
||||
export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
|
||||
|
||||
getRootHostContext(rootContainerInstance : C) : CX,
|
||||
getChildHostContext(parentHostContext : CX, type : T) : CX,
|
||||
@@ -53,9 +53,9 @@ export type HostConfig<T, P, I, TI, PI, C, CX> = {
|
||||
appendInitialChild(parentInstance : I, child : I | TI) : void,
|
||||
finalizeInitialChildren(parentInstance : I, type : T, props : P, rootContainerInstance : C) : boolean,
|
||||
|
||||
prepareUpdate(instance : I, type : T, oldProps : P, newProps : P, hostContext : CX) : boolean,
|
||||
commitUpdate(instance : I, type : T, oldProps : P, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
|
||||
commitMount(instance : I, type : T, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
|
||||
prepareUpdate(instance : I, type : T, oldProps : P, newProps : P, rootContainerInstance : C, hostContext : CX) : null | PL,
|
||||
commitUpdate(instance : I, updatePayload : PL, type : T, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
|
||||
commitMount(instance : I, type : T, newProps : P, internalInstanceHandle : OpaqueNode) : void,
|
||||
|
||||
shouldSetTextContent(props : P) : boolean,
|
||||
resetTextContent(instance : I) : void,
|
||||
@@ -102,7 +102,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
|
||||
parentContext;
|
||||
});
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) : Reconciler<C, I, TI> {
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P, I, TI, PI, C, CX, PL>) : Reconciler<C, I, TI> {
|
||||
|
||||
var {
|
||||
scheduleUpdate,
|
||||
|
||||
@@ -88,7 +88,7 @@ if (__DEV__) {
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) {
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P, I, TI, PI, C, CX, PL>) {
|
||||
const hostContext = ReactFiberHostContext(config);
|
||||
const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
|
||||
const { beginWork, beginFailedWork } = ReactFiberBeginWork(
|
||||
@@ -104,7 +104,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, T
|
||||
commitWork,
|
||||
commitLifeCycles,
|
||||
commitRef,
|
||||
} = ReactFiberCommitWork(config, hostContext, captureError);
|
||||
} = ReactFiberCommitWork(config, captureError);
|
||||
const {
|
||||
scheduleAnimationCallback: hostScheduleAnimationCallback,
|
||||
scheduleDeferredCallback: hostScheduleDeferredCallback,
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
'use strict';
|
||||
|
||||
var { HostComponent } = require('ReactTypeOfWork');
|
||||
var { getNodeFromInstance, getInstanceFromNode } = require('EventPluginUtils');
|
||||
|
||||
function getParent(inst) {
|
||||
if (inst._hostParent !== undefined) {
|
||||
@@ -27,13 +26,8 @@ function getParent(inst) {
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
// Going through the Host Node will guarantee that we get the "current"
|
||||
// Fiber, instead of the alternate because that pointer is updated when
|
||||
// props update.
|
||||
// TODO: This is a bit hacky and possibly slow. We should ideally have
|
||||
// something in the reconciler that allow us to do this safely.
|
||||
if (inst) {
|
||||
return getInstanceFromNode(getNodeFromInstance(inst));
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -126,7 +126,13 @@ var EventPluginHub = {
|
||||
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
|
||||
// live here; needs to be moved to a better place soon
|
||||
if (typeof inst.tag === 'number') {
|
||||
const props = inst.memoizedProps;
|
||||
const props = EventPluginUtils.getFiberEventHandlersFromNode(
|
||||
inst.stateNode
|
||||
);
|
||||
if (!props) {
|
||||
// Work in progress.
|
||||
return null;
|
||||
}
|
||||
listener = props[registrationName];
|
||||
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
|
||||
return null;
|
||||
|
||||
@@ -219,6 +219,9 @@ var EventPluginUtils = {
|
||||
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
|
||||
hasDispatches: hasDispatches,
|
||||
|
||||
getFiberEventHandlersFromNode: function(node) {
|
||||
return ComponentTree.getFiberEventHandlersFromNode(node);
|
||||
},
|
||||
getInstanceFromNode: function(node) {
|
||||
return ComponentTree.getInstanceFromNode(node);
|
||||
},
|
||||
|
||||
@@ -47,6 +47,8 @@ type TextInstance = {|
|
||||
tag : 'TEXT',
|
||||
|};
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
var TestRenderer = ReactFiberReconciler({
|
||||
getRootHostContext() {
|
||||
return emptyObject;
|
||||
@@ -102,17 +104,18 @@ var TestRenderer = ReactFiberReconciler({
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : Object,
|
||||
) : boolean {
|
||||
return true;
|
||||
) : null | {} {
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
instance : Instance,
|
||||
updatePayload : {},
|
||||
type : string,
|
||||
oldProps : Props,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Container,
|
||||
internalInstanceHandle : Object,
|
||||
) : void {
|
||||
instance.type = type;
|
||||
@@ -123,7 +126,6 @@ var TestRenderer = ReactFiberReconciler({
|
||||
instance : Instance,
|
||||
type : string,
|
||||
newProps : Props,
|
||||
rootContainerInstance : Object,
|
||||
internalInstanceHandle : Object
|
||||
) : void {
|
||||
// noop
|
||||
|
||||
Reference in New Issue
Block a user