+ To install React, follow the instructions on
+ GitHub.
+
+
+ If you can see this, React is not working right.
+ If you checked out the source from GitHub make sure to run grunt.
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index afa8ed8947ee..67ac99e5f847 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"eslint-plugin-react-internal": "file:eslint-rules",
"fbjs": "^0.8.1",
"fbjs-scripts": "^0.6.0",
- "flow-bin": "^0.26.0",
+ "flow-bin": "^0.27.0",
"glob": "^6.0.1",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js
new file mode 100644
index 000000000000..74369ac3ea7a
--- /dev/null
+++ b/src/renderers/dom/fiber/ReactDOMFiber.js
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactDOMFiber
+ * @flow
+ */
+
+'use strict';
+
+import type { HostChildren } from 'ReactFiberReconciler';
+
+var ReactFiberReconciler = require('ReactFiberReconciler');
+
+type DOMContainerElement = Element & { _reactRootContainer: Object };
+
+type Container = Element;
+type Props = { };
+type Instance = Element;
+
+function recursivelyAppendChildren(parent : Element, child : HostChildren) {
+ if (!child) {
+ return;
+ }
+ /* $FlowFixMe: Element should have this property. */
+ if (child.nodeType === 1) {
+ /* $FlowFixMe: Refinement issue. I don't know how to express different. */
+ parent.appendChild(child);
+ } else {
+ /* As a result of the refinement issue this type isn't known. */
+ let node : any = child;
+ do {
+ recursivelyAppendChildren(parent, node.output);
+ } while (node = node.sibling);
+ }
+}
+
+var DOMRenderer = ReactFiberReconciler({
+
+ updateContainer(container : Container, children : HostChildren) : void {
+ container.innerHTML = '';
+ recursivelyAppendChildren(container, children);
+ },
+
+ createInstance(type : string, props : Props, children : HostChildren) : Instance {
+ const domElement = document.createElement(type);
+ recursivelyAppendChildren(domElement, children);
+ if (typeof props.children === 'string') {
+ domElement.textContent = props.children;
+ }
+ return domElement;
+ },
+
+ prepareUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren) : boolean {
+ return true;
+ },
+
+ commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren) : void {
+ domElement.innerHTML = '';
+ recursivelyAppendChildren(domElement, children);
+ if (typeof newProps.children === 'string') {
+ domElement.textContent = newProps.children;
+ }
+ },
+
+ deleteInstance(instance : Instance) : void {
+ // Noop
+ },
+
+ scheduleHighPriCallback: window.requestAnimationFrame,
+
+ scheduleLowPriCallback: window.requestIdleCallback,
+
+});
+
+var ReactDOM = {
+
+ render(element : ReactElement, container : DOMContainerElement) {
+ if (!container._reactRootContainer) {
+ container._reactRootContainer = DOMRenderer.mountContainer(element, container);
+ } else {
+ DOMRenderer.updateContainer(element, container._reactRootContainer);
+ }
+ },
+
+};
+
+module.exports = ReactDOM;
diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js
index 9214a1865b9f..5682dc9f8a70 100644
--- a/src/renderers/noop/ReactNoop.js
+++ b/src/renderers/noop/ReactNoop.js
@@ -20,33 +20,97 @@
'use strict';
import type { Fiber } from 'ReactFiber';
+import type { HostChildren } from 'ReactFiberReconciler';
var ReactFiberReconciler = require('ReactFiberReconciler');
var scheduledHighPriCallback = null;
var scheduledLowPriCallback = null;
+const TERMINAL_TAG = 99;
+
+type Container = { rootID: number, children: Array };
+type Props = { };
+type Instance = { tag: 99, type: string, id: number, children: Array };
+
+var instanceCounter = 0;
+
+function recursivelyAppendChildren(flatArray : Array, child : HostChildren) {
+ if (!child) {
+ return;
+ }
+ if (child.tag === TERMINAL_TAG) {
+ flatArray.push(child);
+ } else {
+ let node = child;
+ do {
+ recursivelyAppendChildren(flatArray, node.output);
+ } while (node = node.sibling);
+ }
+}
+
+function flattenChildren(children : HostChildren) {
+ const flatArray = [];
+ recursivelyAppendChildren(flatArray, children);
+ return flatArray;
+}
+
var NoopRenderer = ReactFiberReconciler({
- createHostInstance() {
+ updateContainer(containerInfo : Container, children : HostChildren) : void {
+ console.log('Update container #' + containerInfo.rootID);
+ containerInfo.children = flattenChildren(children);
+ },
+ createInstance(type : string, props : Props, children : HostChildren) : Instance {
+ console.log('Create instance #' + instanceCounter);
+ const inst = {
+ tag: TERMINAL_TAG,
+ id: instanceCounter++,
+ type: type,
+ children: flattenChildren(children),
+ };
+ // Hide from unit tests
+ Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
+ Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
+ return inst;
},
+
+ prepareUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren) : boolean {
+ console.log('Prepare for update on #' + instance.id);
+ return true;
+ },
+
+ commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren) : void {
+ console.log('Commit update on #' + instance.id);
+ instance.children = flattenChildren(children);
+ },
+
+ deleteInstance(instance : Instance) : void {
+ console.log('Delete #' + instance.id);
+ },
+
scheduleHighPriCallback(callback) {
scheduledHighPriCallback = callback;
},
+
scheduleLowPriCallback(callback) {
scheduledLowPriCallback = callback;
},
});
+var rootContainer = { rootID: 0, children: [] };
+
var root = null;
var ReactNoop = {
+ root: rootContainer,
+
render(element : ReactElement) {
if (!root) {
- root = NoopRenderer.mountContainer(element, null);
+ root = NoopRenderer.mountContainer(element, rootContainer);
} else {
NoopRenderer.updateContainer(element, root);
}
@@ -91,6 +155,19 @@ var ReactNoop = {
console.log('Nothing rendered yet.');
return;
}
+
+ function logHostInstances(children: Array, depth) {
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ console.log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
+ logHostInstances(child.children, depth + 1);
+ }
+ }
+ function logContainer(container : Container, depth) {
+ console.log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
+ logHostInstances(container.children, depth + 1);
+ }
+
function logFiber(fiber : Fiber, depth) {
console.log(' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'), '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']');
if (fiber.child) {
@@ -100,6 +177,10 @@ var ReactNoop = {
logFiber(fiber.sibling, depth);
}
}
+
+ console.log('HOST INSTANCES:');
+ logContainer(rootContainer, 0);
+ console.log('FIBERS:');
logFiber((root.stateNode : any).current, 0);
},
diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js
index 48363ac2a053..39683f001e55 100644
--- a/src/renderers/shared/fiber/ReactFiber.js
+++ b/src/renderers/shared/fiber/ReactFiber.js
@@ -48,7 +48,7 @@ type Instance = {
type: any,
// The local state associated with this fiber.
- stateNode: ?Object,
+ stateNode: any,
// Conceptual aliases
// parent : Instance -> return The parent happens to be the same as the
@@ -82,6 +82,16 @@ export type Fiber = Instance & {
// if this returns multiple values. Such as a fragment.
output: any, // This type will be more specific once we overload the tag.
+ // Singly linked list fast path to the next fiber with side-effects.
+ nextEffect: ?Fiber,
+
+ // The first and last fiber with side-effect within this subtree. This allows
+ // us to reuse a slice of the linked list when we reuse the work done within
+ // this fiber.
+ firstEffect: ?Fiber,
+ lastEffect: ?Fiber,
+
+
// This will be used to quickly determine if a subtree has no pending changes.
pendingWorkPriority: PriorityLevel,
@@ -129,6 +139,10 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
memoizedProps: null,
output: null,
+ nextEffect: null,
+ firstEffect: null,
+ lastEffect: null,
+
pendingWorkPriority: NoWork,
hasWorkInProgress: false,
@@ -157,6 +171,13 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
alt.ref = alt.ref;
alt.pendingProps = fiber.pendingProps;
alt.pendingWorkPriority = priorityLevel;
+
+ // Whenever we clone, we do so to get a new work in progress.
+ // This ensures that we've reset these in the new tree.
+ alt.nextEffect = null;
+ alt.firstEffect = null;
+ alt.lastEffect = null;
+
return alt;
}
diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js
index ec1d78af0391..4fcf4d87b081 100644
--- a/src/renderers/shared/fiber/ReactFiberBeginWork.js
+++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js
@@ -14,6 +14,7 @@
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
+import type { HostConfig } from 'ReactFiberReconciler';
var ReactChildFiber = require('ReactChildFiber');
var ReactTypeOfWork = require('ReactTypeOfWork');
@@ -33,211 +34,217 @@ var {
} = require('ReactPriorityLevel');
var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork');
-function reconcileChildren(current, workInProgress, nextChildren) {
- const priority = workInProgress.pendingWorkPriority;
- workInProgress.child = ReactChildFiber.reconcileChildFibers(
- workInProgress,
- current ? current.child : null,
- nextChildren,
- priority
- );
-}
-
-function updateFunctionalComponent(current, workInProgress) {
- var fn = workInProgress.type;
- var props = workInProgress.pendingProps;
- console.log('update fn:', fn.name);
- var nextChildren = fn(props);
- reconcileChildren(current, workInProgress, nextChildren);
- workInProgress.pendingWorkPriority = NoWork;
-}
-
-function updateHostComponent(current, workInProgress) {
- console.log('host component', workInProgress.type, typeof workInProgress.pendingProps.children === 'string' ? workInProgress.pendingProps.children : '');
-
- var nextChildren = workInProgress.pendingProps.children;
-
- let priority = workInProgress.pendingWorkPriority;
- if (workInProgress.pendingProps.hidden && priority !== OffscreenPriority) {
- // If this host component is hidden, we can reconcile its children at
- // the lowest priority and bail out from this particular pass. Unless, we're
- // currently reconciling the lowest priority.
- workInProgress.child = ReactChildFiber.reconcileChildFibers(
- workInProgress,
- current ? current.child : null,
- nextChildren,
- OffscreenPriority
- );
- workInProgress.pendingWorkPriority = OffscreenPriority;
- return null;
- } else {
+module.exports = function(config : HostConfig) {
+
+ function reconcileChildren(current, workInProgress, nextChildren) {
+ const priority = workInProgress.pendingWorkPriority;
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
current ? current.child : null,
nextChildren,
priority
);
+ }
+
+ function updateFunctionalComponent(current, workInProgress) {
+ var fn = workInProgress.type;
+ var props = workInProgress.pendingProps;
+ console.log('update fn:', fn.name);
+ var nextChildren = fn(props);
+ reconcileChildren(current, workInProgress, nextChildren);
workInProgress.pendingWorkPriority = NoWork;
- return workInProgress.child;
}
-}
-
-function mountIndeterminateComponent(current, workInProgress) {
- var fn = workInProgress.type;
- var props = workInProgress.pendingProps;
- var value = fn(props);
- if (typeof value === 'object' && value && typeof value.render === 'function') {
- console.log('performed work on class:', fn.name);
- // Proceed under the assumption that this is a class instance
- workInProgress.tag = ClassComponent;
- if (workInProgress.alternate) {
- workInProgress.alternate.tag = ClassComponent;
- }
- } else {
- console.log('performed work on fn:', fn.name);
- // Proceed under the assumption that this is a functional component
- workInProgress.tag = FunctionalComponent;
- if (workInProgress.alternate) {
- workInProgress.alternate.tag = FunctionalComponent;
+
+ function updateHostComponent(current, workInProgress) {
+ console.log('host component', workInProgress.type, typeof workInProgress.pendingProps.children === 'string' ? workInProgress.pendingProps.children : '');
+
+ var nextChildren = workInProgress.pendingProps.children;
+
+ let priority = workInProgress.pendingWorkPriority;
+ if (workInProgress.pendingProps.hidden && priority !== OffscreenPriority) {
+ // If this host component is hidden, we can reconcile its children at
+ // the lowest priority and bail out from this particular pass. Unless, we're
+ // currently reconciling the lowest priority.
+ workInProgress.child = ReactChildFiber.reconcileChildFibers(
+ workInProgress,
+ current ? current.child : null,
+ nextChildren,
+ OffscreenPriority
+ );
+ workInProgress.pendingWorkPriority = OffscreenPriority;
+ return null;
+ } else {
+ workInProgress.child = ReactChildFiber.reconcileChildFibers(
+ workInProgress,
+ current ? current.child : null,
+ nextChildren,
+ priority
+ );
+ workInProgress.pendingWorkPriority = NoWork;
+ return workInProgress.child;
}
}
- reconcileChildren(current, workInProgress, value);
- workInProgress.pendingWorkPriority = NoWork;
-}
-
-function updateCoroutineComponent(current, workInProgress) {
- var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
- if (!coroutine) {
- throw new Error('Should be resolved by now');
- }
- console.log('begin coroutine', workInProgress.type.name);
- reconcileChildren(current, workInProgress, coroutine.children);
- workInProgress.pendingWorkPriority = NoWork;
-}
-
-function reuseChildren(returnFiber : Fiber, firstChild : Fiber) {
- // TODO: None of this should be necessary if structured better.
- // The returnFiber pointer only needs to be updated when we walk into this child
- // which we don't do right now. If the pending work priority indicated only
- // if a child has work rather than if the node has work, then we would know
- // by a single lookup on workInProgress rather than having to go through
- // each child.
- let child = firstChild;
- do {
- // Update the returnFiber of the child to the newest fiber.
- child.return = returnFiber;
- // Retain the priority if there's any work left to do in the children.
- if (child.pendingWorkPriority !== NoWork &&
- (returnFiber.pendingWorkPriority === NoWork ||
- returnFiber.pendingWorkPriority > child.pendingWorkPriority)) {
- returnFiber.pendingWorkPriority = child.pendingWorkPriority;
- }
- } while (child = child.sibling);
-}
-
-function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
- // The current, flushed, state of this fiber is the alternate.
- // Ideally nothing should rely on this, but relying on it here
- // means that we don't need an additional field on the work in
- // progress.
- if (current && workInProgress.pendingProps === current.memoizedProps) {
- // The most likely scenario is that the previous copy of the tree contains
- // the same props as the new one. In that case, we can just copy the output
- // and children from that node.
- workInProgress.memoizedProps = workInProgress.pendingProps;
- workInProgress.output = current.output;
- const priorityLevel = workInProgress.pendingWorkPriority;
- workInProgress.pendingProps = null;
- workInProgress.pendingWorkPriority = NoWork;
- workInProgress.stateNode = current.stateNode;
- if (current.child) {
- // If we bail out but still has work with the current priority in this
- // subtree, we need to go find it right now. If we don't, we won't flush
- // it until the next tick.
- workInProgress.child = current.child;
- reuseChildren(workInProgress, workInProgress.child);
- if (workInProgress.pendingWorkPriority <= priorityLevel) {
- // TODO: This passes the current node and reads the priority level and
- // pending props from that. We want it to read our priority level and
- // pending props from the work in progress. Needs restructuring.
- return findNextUnitOfWorkAtPriority(workInProgress.alternate, priorityLevel);
- } else {
- return null;
+
+ function mountIndeterminateComponent(current, workInProgress) {
+ var fn = workInProgress.type;
+ var props = workInProgress.pendingProps;
+ var value = fn(props);
+ if (typeof value === 'object' && value && typeof value.render === 'function') {
+ console.log('performed work on class:', fn.name);
+ // Proceed under the assumption that this is a class instance
+ workInProgress.tag = ClassComponent;
+ if (workInProgress.alternate) {
+ workInProgress.alternate.tag = ClassComponent;
}
} else {
- workInProgress.child = null;
- return null;
+ console.log('performed work on fn:', fn.name);
+ // Proceed under the assumption that this is a functional component
+ workInProgress.tag = FunctionalComponent;
+ if (workInProgress.alternate) {
+ workInProgress.alternate.tag = FunctionalComponent;
+ }
}
+ reconcileChildren(current, workInProgress, value);
+ workInProgress.pendingWorkPriority = NoWork;
}
- if (!workInProgress.hasWorkInProgress &&
- workInProgress.pendingProps === workInProgress.memoizedProps &&
- workInProgress.pendingWorkPriority === NoWork) {
- // If we started this work before, and finished it, or if we're in a
- // ping-pong update scenario, this version could already be what we're
- // looking for. In that case, we should be able to just bail out.
- workInProgress.pendingProps = null;
- // TODO: We should be able to bail out if there is remaining work at a lower
- // priority too. However, I don't know if that is safe or even better since
- // the other tree could've potentially finished that work.
- return null;
+ function updateCoroutineComponent(current, workInProgress) {
+ var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
+ if (!coroutine) {
+ throw new Error('Should be resolved by now');
+ }
+ console.log('begin coroutine', workInProgress.type.name);
+ reconcileChildren(current, workInProgress, coroutine.children);
+ workInProgress.pendingWorkPriority = NoWork;
}
- workInProgress.hasWorkInProgress = true;
-
- switch (workInProgress.tag) {
- case IndeterminateComponent:
- mountIndeterminateComponent(current, workInProgress);
- return workInProgress.child;
- case FunctionalComponent:
- updateFunctionalComponent(current, workInProgress);
- return workInProgress.child;
- case ClassComponent:
- console.log('class component', workInProgress.pendingProps.type.name);
- return workInProgress.child;
- case HostContainer:
- reconcileChildren(current, workInProgress, workInProgress.pendingProps);
- // A yield component is just a placeholder, we can just run through the
- // next one immediately.
- workInProgress.pendingWorkPriority = NoWork;
- if (workInProgress.child) {
- return beginWork(
- workInProgress.child.alternate,
- workInProgress.child
- );
- }
- return null;
- case HostComponent:
- return updateHostComponent(current, workInProgress);
- case CoroutineHandlerPhase:
- // This is a restart. Reset the tag to the initial phase.
- workInProgress.tag = CoroutineComponent;
- // Intentionally fall through since this is now the same.
- case CoroutineComponent:
- updateCoroutineComponent(current, workInProgress);
- // This doesn't take arbitrary time so we could synchronously just begin
- // eagerly do the work of workInProgress.child as an optimization.
- if (workInProgress.child) {
- return beginWork(
- workInProgress.child.alternate,
- workInProgress.child
- );
+ function reuseChildren(returnFiber : Fiber, firstChild : Fiber) {
+ // TODO: None of this should be necessary if structured better.
+ // The returnFiber pointer only needs to be updated when we walk into this child
+ // which we don't do right now. If the pending work priority indicated only
+ // if a child has work rather than if the node has work, then we would know
+ // by a single lookup on workInProgress rather than having to go through
+ // each child.
+ let child = firstChild;
+ do {
+ // Update the returnFiber of the child to the newest fiber.
+ child.return = returnFiber;
+ // Retain the priority if there's any work left to do in the children.
+ if (child.pendingWorkPriority !== NoWork &&
+ (returnFiber.pendingWorkPriority === NoWork ||
+ returnFiber.pendingWorkPriority > child.pendingWorkPriority)) {
+ returnFiber.pendingWorkPriority = child.pendingWorkPriority;
}
- return workInProgress.child;
- case YieldComponent:
- // A yield component is just a placeholder, we can just run through the
- // next one immediately.
+ } while (child = child.sibling);
+ }
+
+ function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
+ // The current, flushed, state of this fiber is the alternate.
+ // Ideally nothing should rely on this, but relying on it here
+ // means that we don't need an additional field on the work in
+ // progress.
+ if (current && workInProgress.pendingProps === current.memoizedProps) {
+ // The most likely scenario is that the previous copy of the tree contains
+ // the same props as the new one. In that case, we can just copy the output
+ // and children from that node.
+ workInProgress.memoizedProps = workInProgress.pendingProps;
+ workInProgress.output = current.output;
+ const priorityLevel = workInProgress.pendingWorkPriority;
+ workInProgress.pendingProps = null;
workInProgress.pendingWorkPriority = NoWork;
- if (workInProgress.sibling) {
- return beginWork(
- workInProgress.sibling.alternate,
- workInProgress.sibling
- );
+ workInProgress.stateNode = current.stateNode;
+ if (current.child) {
+ // If we bail out but still has work with the current priority in this
+ // subtree, we need to go find it right now. If we don't, we won't flush
+ // it until the next tick.
+ workInProgress.child = current.child;
+ reuseChildren(workInProgress, workInProgress.child);
+ if (workInProgress.pendingWorkPriority <= priorityLevel) {
+ // TODO: This passes the current node and reads the priority level and
+ // pending props from that. We want it to read our priority level and
+ // pending props from the work in progress. Needs restructuring.
+ return findNextUnitOfWorkAtPriority(workInProgress.alternate, priorityLevel);
+ } else {
+ return null;
+ }
+ } else {
+ workInProgress.child = null;
+ return null;
}
+ }
+
+ if (!workInProgress.hasWorkInProgress &&
+ workInProgress.pendingProps === workInProgress.memoizedProps &&
+ workInProgress.pendingWorkPriority === NoWork) {
+ // If we started this work before, and finished it, or if we're in a
+ // ping-pong update scenario, this version could already be what we're
+ // looking for. In that case, we should be able to just bail out.
+ workInProgress.pendingProps = null;
+ // TODO: We should be able to bail out if there is remaining work at a lower
+ // priority too. However, I don't know if that is safe or even better since
+ // the other tree could've potentially finished that work.
return null;
- default:
- throw new Error('Unknown unit of work tag');
+ }
+
+ workInProgress.hasWorkInProgress = true;
+
+ switch (workInProgress.tag) {
+ case IndeterminateComponent:
+ mountIndeterminateComponent(current, workInProgress);
+ return workInProgress.child;
+ case FunctionalComponent:
+ updateFunctionalComponent(current, workInProgress);
+ return workInProgress.child;
+ case ClassComponent:
+ console.log('class component', workInProgress.pendingProps.type.name);
+ return workInProgress.child;
+ case HostContainer:
+ reconcileChildren(current, workInProgress, workInProgress.pendingProps);
+ // A yield component is just a placeholder, we can just run through the
+ // next one immediately.
+ workInProgress.pendingWorkPriority = NoWork;
+ if (workInProgress.child) {
+ return beginWork(
+ workInProgress.child.alternate,
+ workInProgress.child
+ );
+ }
+ return null;
+ case HostComponent:
+ return updateHostComponent(current, workInProgress);
+ case CoroutineHandlerPhase:
+ // This is a restart. Reset the tag to the initial phase.
+ workInProgress.tag = CoroutineComponent;
+ // Intentionally fall through since this is now the same.
+ case CoroutineComponent:
+ updateCoroutineComponent(current, workInProgress);
+ // This doesn't take arbitrary time so we could synchronously just begin
+ // eagerly do the work of workInProgress.child as an optimization.
+ if (workInProgress.child) {
+ return beginWork(
+ workInProgress.child.alternate,
+ workInProgress.child
+ );
+ }
+ return workInProgress.child;
+ case YieldComponent:
+ // A yield component is just a placeholder, we can just run through the
+ // next one immediately.
+ workInProgress.pendingWorkPriority = NoWork;
+ if (workInProgress.sibling) {
+ return beginWork(
+ workInProgress.sibling.alternate,
+ workInProgress.sibling
+ );
+ }
+ return null;
+ default:
+ throw new Error('Unknown unit of work tag');
+ }
}
-}
-exports.beginWork = beginWork;
+ return {
+ beginWork,
+ };
+
+};
diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js
new file mode 100644
index 000000000000..7792be2102bb
--- /dev/null
+++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactFiberCommitWork
+ * @flow
+ */
+
+'use strict';
+
+import type { Fiber } from 'ReactFiber';
+import type { FiberRoot } from 'ReactFiberRoot';
+import type { HostConfig } from 'ReactFiberReconciler';
+
+var ReactTypeOfWork = require('ReactTypeOfWork');
+var {
+ ClassComponent,
+ HostContainer,
+ HostComponent,
+} = ReactTypeOfWork;
+
+module.exports = function(config : HostConfig) {
+
+ const updateContainer = config.updateContainer;
+ const commitUpdate = config.commitUpdate;
+
+ function commitWork(finishedWork : Fiber) : void {
+ switch (finishedWork.tag) {
+ case ClassComponent: {
+ // TODO: Fire componentDidMount/componentDidUpdate, update refs
+ return;
+ }
+ case HostContainer: {
+ // TODO: Attach children to root container.
+ const children = finishedWork.output;
+ const root : FiberRoot = finishedWork.stateNode;
+ const containerInfo : C = root.containerInfo;
+ updateContainer(containerInfo, children);
+ return;
+ }
+ case HostComponent: {
+ if (finishedWork.stateNode == null || !finishedWork.alternate) {
+ throw new Error('This should only be done during updates.');
+ }
+ // Commit the work prepared earlier.
+ const child = (finishedWork.child : ?Fiber);
+ const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
+ const newProps = finishedWork.memoizedProps;
+ const current = finishedWork.alternate;
+ const oldProps = current.memoizedProps;
+ const instance : I = finishedWork.stateNode;
+ commitUpdate(instance, oldProps, newProps, children);
+ return;
+ }
+ default:
+ throw new Error('This unit of work tag should not have side-effects.');
+ }
+ }
+
+ return {
+ commitWork,
+ };
+
+};
diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
index d816510b4001..9900a5b9fb9d 100644
--- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
@@ -14,7 +14,7 @@
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
-
+import type { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';
var ReactChildFiber = require('ReactChildFiber');
@@ -30,102 +30,162 @@ var {
YieldComponent,
} = ReactTypeOfWork;
-function transferOutput(child : ?Fiber, returnFiber : Fiber) {
- // If we have a single result, we just pass that through as the output to
- // avoid unnecessary traversal. When we have multiple output, we just pass
- // the linked list of fibers that has the individual output values.
- returnFiber.output = (child && !child.sibling) ? child.output : child;
- returnFiber.memoizedProps = returnFiber.pendingProps;
-}
-
-function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
- if (!output) {
- // Ignore nulls etc.
- } else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
- // Detect if this is a fiber, if so it is a fragment result.
- // $FlowFixMe: Refinement issue.
- var item = (output : Fiber);
- do {
- recursivelyFillYields(yields, item.output);
- item = item.sibling;
- } while (item);
- } else {
- // $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
- yields.push(output);
+module.exports = function(config : HostConfig) {
+
+ const createInstance = config.createInstance;
+ const prepareUpdate = config.prepareUpdate;
+
+ function markForPreEffect(workInProgress : Fiber) {
+ // Schedule a side-effect on this fiber, BEFORE the children's side-effects.
+ if (workInProgress.firstEffect) {
+ workInProgress.nextEffect = workInProgress.firstEffect;
+ workInProgress.firstEffect = workInProgress;
+ } else {
+ workInProgress.firstEffect = workInProgress;
+ workInProgress.lastEffect = workInProgress;
+ }
+ }
+
+ /*
+ function markForPostEffect(workInProgress : Fiber) {
+ // Schedule a side-effect on this fiber, AFTER the children's side-effects.
+ if (workInProgress.lastEffect) {
+ workInProgress.lastEffect.nextEffect = workInProgress;
+ } else {
+ workInProgress.firstEffect = workInProgress;
+ }
+ workInProgress.lastEffect = workInProgress;
+ }
+ */
+
+ function transferOutput(child : ?Fiber, returnFiber : Fiber) {
+ // If we have a single result, we just pass that through as the output to
+ // avoid unnecessary traversal. When we have multiple output, we just pass
+ // the linked list of fibers that has the individual output values.
+ returnFiber.output = (child && !child.sibling) ? child.output : child;
+ returnFiber.memoizedProps = returnFiber.pendingProps;
}
-}
-function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
- var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
- if (!coroutine) {
- throw new Error('Should be resolved by now');
+ function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
+ if (!output) {
+ // Ignore nulls etc.
+ } else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
+ // Detect if this is a fiber, if so it is a fragment result.
+ // $FlowFixMe: Refinement issue.
+ var item = (output : Fiber);
+ do {
+ recursivelyFillYields(yields, item.output);
+ item = item.sibling;
+ } while (item);
+ } else {
+ // $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
+ yields.push(output);
+ }
}
- // First step of the coroutine has completed. Now we need to do the second.
- // TODO: It would be nice to have a multi stage coroutine represented by a
- // single component, or at least tail call optimize nested ones. Currently
- // that requires additional fields that we don't want to add to the fiber.
- // So this requires nested handlers.
- // Note: This doesn't mutate the alternate node. I don't think it needs to
- // since this stage is reset for every pass.
- workInProgress.tag = CoroutineHandlerPhase;
-
- // Build up the yields.
- // TODO: Compare this to a generator or opaque helpers like Children.
- var yields : Array = [];
- var child = workInProgress.child;
- while (child) {
- recursivelyFillYields(yields, child.output);
- child = child.sibling;
+ function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
+ var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
+ if (!coroutine) {
+ throw new Error('Should be resolved by now');
+ }
+
+ // First step of the coroutine has completed. Now we need to do the second.
+ // TODO: It would be nice to have a multi stage coroutine represented by a
+ // single component, or at least tail call optimize nested ones. Currently
+ // that requires additional fields that we don't want to add to the fiber.
+ // So this requires nested handlers.
+ // Note: This doesn't mutate the alternate node. I don't think it needs to
+ // since this stage is reset for every pass.
+ workInProgress.tag = CoroutineHandlerPhase;
+
+ // Build up the yields.
+ // TODO: Compare this to a generator or opaque helpers like Children.
+ var yields : Array = [];
+ var child = workInProgress.child;
+ while (child) {
+ recursivelyFillYields(yields, child.output);
+ child = child.sibling;
+ }
+ var fn = coroutine.handler;
+ var props = coroutine.props;
+ var nextChildren = fn(props, yields);
+
+ var currentFirstChild = current ? current.stateNode : null;
+ // Inherit the priority of the returnFiber.
+ const priority = workInProgress.pendingWorkPriority;
+ workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
+ workInProgress,
+ currentFirstChild,
+ nextChildren,
+ priority
+ );
+ return workInProgress.stateNode;
}
- var fn = coroutine.handler;
- var props = coroutine.props;
- var nextChildren = fn(props, yields);
-
- var currentFirstChild = current ? current.stateNode : null;
- // Inherit the priority of the returnFiber.
- const priority = workInProgress.pendingWorkPriority;
- workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
- workInProgress,
- currentFirstChild,
- nextChildren,
- priority
- );
- return workInProgress.stateNode;
-}
-
-exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
- switch (workInProgress.tag) {
- case FunctionalComponent:
- console.log('/functional component', workInProgress.type.name);
- transferOutput(workInProgress.child, workInProgress);
- return null;
- case ClassComponent:
- console.log('/class component', workInProgress.type.name);
- transferOutput(workInProgress.child, workInProgress);
- return null;
- case HostContainer:
- return null;
- case HostComponent:
- transferOutput(workInProgress.child, workInProgress);
- console.log('/host component', workInProgress.type);
- return null;
- case CoroutineComponent:
- console.log('/coroutine component', workInProgress.pendingProps.handler.name);
- return moveCoroutineToHandlerPhase(current, workInProgress);
- case CoroutineHandlerPhase:
- transferOutput(workInProgress.stateNode, workInProgress);
- // Reset the tag to now be a first phase coroutine.
- workInProgress.tag = CoroutineComponent;
- return null;
- case YieldComponent:
- // Does nothing.
- return null;
-
- // Error cases
- case IndeterminateComponent:
- throw new Error('An indeterminate component should have become determinate before completing.');
- default:
- throw new Error('Unknown unit of work tag');
+
+ function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
+ switch (workInProgress.tag) {
+ case FunctionalComponent:
+ console.log('/functional component', workInProgress.type.name);
+ transferOutput(workInProgress.child, workInProgress);
+ return null;
+ case ClassComponent:
+ console.log('/class component', workInProgress.type.name);
+ transferOutput(workInProgress.child, workInProgress);
+ return null;
+ case HostContainer:
+ transferOutput(workInProgress.child, workInProgress);
+ // We don't know if a container has updated any children so we always
+ // need to update it right now. We schedule this side-effect before
+ // all the other side-effects in the subtree. We need to schedule it
+ // before so that the entire tree is up-to-date before the life-cycles
+ // are invoked.
+ markForPreEffect(workInProgress);
+ return null;
+ case HostComponent:
+ console.log('/host component', workInProgress.type);
+ const child = workInProgress.child;
+ const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
+ const newProps = workInProgress.pendingProps;
+ workInProgress.memoizedProps = newProps;
+ if (current && workInProgress.stateNode != null) {
+ // If we have an alternate, that means this is an update and we need to
+ // schedule a side-effect to do the updates.
+ const oldProps = current.memoizedProps;
+ const instance : I = workInProgress.stateNode;
+ if (prepareUpdate(instance, oldProps, newProps, children)) {
+ // This returns true if there was something to update.
+ markForPreEffect(workInProgress);
+ }
+ workInProgress.output = instance;
+ } else {
+ const instance = createInstance(workInProgress.type, newProps, children);
+ // TODO: This seems like unnecessary duplication.
+ workInProgress.stateNode = instance;
+ workInProgress.output = instance;
+ }
+ return null;
+ case CoroutineComponent:
+ console.log('/coroutine component', workInProgress.pendingProps.handler.name);
+ return moveCoroutineToHandlerPhase(current, workInProgress);
+ case CoroutineHandlerPhase:
+ transferOutput(workInProgress.stateNode, workInProgress);
+ // Reset the tag to now be a first phase coroutine.
+ workInProgress.tag = CoroutineComponent;
+ return null;
+ case YieldComponent:
+ // Does nothing.
+ return null;
+
+ // Error cases
+ case IndeterminateComponent:
+ throw new Error('An indeterminate component should have become determinate before completing.');
+ default:
+ throw new Error('Unknown unit of work tag');
+ }
}
+
+ return {
+ completeWork,
+ };
+
};
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index 3fde615cf0ec..cb22e88421dc 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -14,6 +14,7 @@
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
+import type { TypeOfWork } from 'ReactTypeOfWork';
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');
@@ -22,18 +23,27 @@ var {
LowPriority,
} = require('ReactPriorityLevel');
-type ReactHostElement = {
- type: T,
- props: P
-};
-
type Deadline = {
timeRemaining : () => number
};
-export type HostConfig = {
+type HostChildNode = { tag: TypeOfWork, output: HostChildren, sibling: any };
+
+export type HostChildren = null | void | I | HostChildNode;
+
+export type HostConfig = {
+
+ // TODO: We don't currently have a quick way to detect that children didn't
+ // reorder so we host will always need to check the set. We should make a flag
+ // or something so that it can bailout easily.
+
+ updateContainer(containerInfo : C, children : HostChildren) : void;
+
+ createInstance(type : T, props : P, children : HostChildren) : I,
+ prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren) : bool,
+ commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren) : void,
+ deleteInstance(instance : I) : void,
- createHostInstance(element : ReactHostElement) : I,
scheduleHighPriCallback(callback : () => void) : void,
scheduleLowPriCallback(callback : (deadline : Deadline) => void) : void
@@ -41,22 +51,22 @@ export type HostConfig = {
type OpaqueNode = Fiber;
-export type Reconciler = {
- mountContainer(element : ReactElement, containerInfo : ?Object) : OpaqueNode,
+export type Reconciler = {
+ mountContainer(element : ReactElement, containerInfo : C) : OpaqueNode,
updateContainer(element : ReactElement, container : OpaqueNode) : void,
unmountContainer(container : OpaqueNode) : void,
// Used to extract the return value from the initial render. Legacy API.
- getPublicRootInstance(container : OpaqueNode) : ?Object,
+ getPublicRootInstance(container : OpaqueNode) : (C | null),
};
-module.exports = function(config : HostConfig) : Reconciler {
+module.exports = function(config : HostConfig) : Reconciler {
var { scheduleLowPriWork } = ReactFiberScheduler(config);
return {
- mountContainer(element : ReactElement, containerInfo : ?Object) : OpaqueNode {
+ mountContainer(element : ReactElement, containerInfo : C) : OpaqueNode {
const root = createFiberRoot(containerInfo);
const container = root.current;
// TODO: Use pending work/state instead of props.
@@ -91,7 +101,7 @@ module.exports = function(config : HostConfig) : Reconciler {
scheduleLowPriWork(root);
},
- getPublicRootInstance(container : OpaqueNode) : ?Object {
+ getPublicRootInstance(container : OpaqueNode) : (C | null) {
return null;
},
diff --git a/src/renderers/shared/fiber/ReactFiberRoot.js b/src/renderers/shared/fiber/ReactFiberRoot.js
index ce186c3fced2..c0b312253cc4 100644
--- a/src/renderers/shared/fiber/ReactFiberRoot.js
+++ b/src/renderers/shared/fiber/ReactFiberRoot.js
@@ -18,7 +18,7 @@ const { createHostContainerFiber } = require('ReactFiber');
export type FiberRoot = {
// Any additional information from the host associated with this root.
- containerInfo: ?Object,
+ containerInfo: any,
// The currently active root fiber. This is the mutable root of the tree.
current: Fiber,
// Determines if this root has already been added to the schedule for work.
@@ -27,7 +27,7 @@ export type FiberRoot = {
nextScheduledRoot: ?FiberRoot,
};
-exports.createFiberRoot = function(containerInfo : ?Object) : FiberRoot {
+exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostContainerFiber();
diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js
index 4b6da086bf92..6df6198f9fe6 100644
--- a/src/renderers/shared/fiber/ReactFiberScheduler.js
+++ b/src/renderers/shared/fiber/ReactFiberScheduler.js
@@ -16,9 +16,11 @@ import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
+var ReactFiberBeginWork = require('ReactFiberBeginWork');
+var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
+var ReactFiberCommitWork = require('ReactFiberCommitWork');
+
var { cloneFiber } = require('ReactFiber');
-var { beginWork } = require('ReactFiberBeginWork');
-var { completeWork } = require('ReactFiberCompleteWork');
var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork');
var {
@@ -30,7 +32,11 @@ var {
var timeHeuristicForUnitOfWork = 1;
-module.exports = function(config : HostConfig) {
+module.exports = function(config : HostConfig) {
+
+ const { beginWork } = ReactFiberBeginWork(config);
+ const { completeWork } = ReactFiberCompleteWork(config);
+ const { commitWork } = ReactFiberCommitWork(config);
// const scheduleHighPriCallback = config.scheduleHighPriCallback;
const scheduleLowPriCallback = config.scheduleLowPriCallback;
@@ -78,6 +84,22 @@ module.exports = function(config : HostConfig) {
return null;
}
+ function commitAllWork(finishedWork : Fiber) {
+ // Commit all the side-effects within a tree.
+ // TODO: Error handling.
+ let effectfulFiber = finishedWork.firstEffect;
+ while (effectfulFiber) {
+ commitWork(effectfulFiber);
+ const next = effectfulFiber.nextEffect;
+ // Ensure that we clean these up so that we don't accidentally keep them.
+ // I'm not actually sure this matters because we can't reset firstEffect
+ // and lastEffect since they're on every node, not just the effectful
+ // ones. So we have to clean everything as we reuse nodes anyway.
+ effectfulFiber.nextEffect = null;
+ effectfulFiber = next;
+ }
+ }
+
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
while (true) {
// The current, flushed, state of this fiber is the alternate.
@@ -96,11 +118,25 @@ module.exports = function(config : HostConfig) {
const returnFiber = workInProgress.return;
- // Ensure that remaining work priority bubbles up.
- if (returnFiber && workInProgress.pendingWorkPriority !== NoWork &&
- (returnFiber.pendingWorkPriority === NoWork ||
- returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
- returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority;
+ if (returnFiber) {
+ // Ensure that remaining work priority bubbles up.
+ if (workInProgress.pendingWorkPriority !== NoWork &&
+ (returnFiber.pendingWorkPriority === NoWork ||
+ returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
+ returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority;
+ }
+ // Ensure that the first and last effect of the parent corresponds
+ // to the children's first and last effect. This probably relies on
+ // children completing in order.
+ if (!returnFiber.firstEffect) {
+ returnFiber.firstEffect = workInProgress.firstEffect;
+ }
+ if (workInProgress.lastEffect) {
+ if (returnFiber.lastEffect) {
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
+ }
+ returnFiber.lastEffect = workInProgress.lastEffect;
+ }
}
if (next) {
@@ -121,6 +157,7 @@ module.exports = function(config : HostConfig) {
// also ensures that work scheduled during reconciliation gets deferred.
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
console.log('----- COMPLETED with remaining work:', workInProgress.pendingWorkPriority);
+ commitAllWork(workInProgress);
const nextWork = findNextUnitOfWork();
// if (!nextWork && hasMoreWork) {
// TODO: This can happen when some deep work completes and we don't
diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
new file mode 100644
index 000000000000..c59c12b97d31
--- /dev/null
+++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+var React;
+var ReactNoop;
+
+describe('ReactIncremental', function() {
+ beforeEach(function() {
+ React = require('React');
+ ReactNoop = require('ReactNoop');
+ spyOn(console, 'log');
+ });
+
+ function div(...children) {
+ return { type: 'div', children };
+ }
+
+ function span(...children) {
+ return { type: 'span', children };
+ }
+
+ it('can update child nodes of a host instance', function() {
+
+ function Bar(props) {
+ return {props.text};
+ }
+
+ function Foo(props) {
+ return (
+
+ );
+ }
+
+ ReactNoop.render();
+ ReactNoop.flush();
+ expect(ReactNoop.root.children).toEqual([
+ div(div(span(), span()), span()),
+ ]);
+
+ ReactNoop.render();
+ ReactNoop.flushLowPri(35);
+ expect(ReactNoop.root.children).toEqual([
+ div(div(span(), span()), span()),
+ ]);
+
+ });
+
+ // TODO: Test that side-effects are not cut off when a work in progress node
+ // moves to "current" without flushing due to having lower priority. Does this
+ // even happen? Maybe a child doesn't get processed because it is lower prio?
+
+});