Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy

[version]
^0.26.0
^0.27.0
44 changes: 44 additions & 0 deletions examples/fiber/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Fiber Example</title>
<link rel="stylesheet" href="../shared/css/base.css" />
</head>
<body>
<h1>Fiber Example</h1>
<div id="container">
<p>
To install React, follow the instructions on
<a href="https://github.com/facebook/react/">GitHub</a>.
</p>
<p>
If you can see this, React is <strong>not</strong> working right.
If you checked out the source from GitHub make sure to run <code>grunt</code>.
</p>
</div>
<script src="../../build/react.js"></script>
<script src="../../build/react-dom.js"></script>
<script>
function ExampleApplication(props) {
var elapsed = Math.round(props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
var message =
'React has been successfully running for ' + seconds + ' seconds.';

return React.DOM.p(null, message);
}

// Call React.createFactory instead of directly call ExampleApplication({...}) in React.render
var ExampleApplicationFactory = React.createFactory(ExampleApplication);

var start = new Date().getTime();
setInterval(function() {
ReactDOM.render(
ExampleApplicationFactory({elapsed: new Date().getTime() - start}),
document.getElementById('container')
);
}, 50);
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
92 changes: 92 additions & 0 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
@@ -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<Instance>) {
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<Instance>) : void {
container.innerHTML = '';
recursivelyAppendChildren(container, children);
},

createInstance(type : string, props : Props, children : HostChildren<Instance>) : 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<Instance>) : boolean {
return true;
},

commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : 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<any>, container : DOMContainerElement) {
if (!container._reactRootContainer) {
container._reactRootContainer = DOMRenderer.mountContainer(element, container);
} else {
DOMRenderer.updateContainer(element, container._reactRootContainer);
}
},

};

module.exports = ReactDOM;
85 changes: 83 additions & 2 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Instance> };
type Props = { };
type Instance = { tag: 99, type: string, id: number, children: Array<Instance> };

var instanceCounter = 0;

function recursivelyAppendChildren(flatArray : Array<Instance>, child : HostChildren<Instance>) {
if (!child) {
return;
}
if (child.tag === TERMINAL_TAG) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really happy with this model of determining terminalness by testing the host environment's type. I'd like to structure the output so that I always know if the next value is terminal or not.

One thing I could do is skip the propagation of single values up the tree and just output the child for those cases. More to traverse but you have to do it at some point I guess. Then I'd just check if the tag is a host node and if so, then I know the output is terminal.

@spicyj Any ideas?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None yet, sorry.

flatArray.push(child);
} else {
let node = child;
do {
recursivelyAppendChildren(flatArray, node.output);
} while (node = node.sibling);
}
}

function flattenChildren(children : HostChildren<Instance>) {
const flatArray = [];
recursivelyAppendChildren(flatArray, children);
return flatArray;
}

var NoopRenderer = ReactFiberReconciler({

createHostInstance() {
updateContainer(containerInfo : Container, children : HostChildren<Instance>) : void {
console.log('Update container #' + containerInfo.rootID);
containerInfo.children = flattenChildren(children);
},

createInstance(type : string, props : Props, children : HostChildren<Instance>) : 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<Instance>) : boolean {
console.log('Prepare for update on #' + instance.id);
return true;
},

commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : 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<any>) {
if (!root) {
root = NoopRenderer.mountContainer(element, null);
root = NoopRenderer.mountContainer(element, rootContainer);
} else {
NoopRenderer.updateContainer(element, root);
}
Expand Down Expand Up @@ -91,6 +155,19 @@ var ReactNoop = {
console.log('Nothing rendered yet.');
return;
}

function logHostInstances(children: Array<Instance>, 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) {
Expand All @@ -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);
},

Expand Down
23 changes: 22 additions & 1 deletion src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the firstEffect field on every node? It seems like we could make lastEffect point to the root or a placeholder instead of being null and then save a field.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any node can end up being reused and then you need the start of that slice of the list.

We could potentially make them non-nullable for type purposes. I'll have to think about that some more. Would pointing to the root by you anything more than pointing to any random placeholder fiber?

For context, a parent can schedule itself before or after its children. That's a feature I take advantage of in subsequent diffs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, no reason to use the root.

What do you mean, "Any node can end up being reused and then you need the start of that slice of the list."?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the work on a bunch of parents was aborted, because a higher priority update touched them, we can still reuse the work done on a child that wasn't touched. But we need to know which side-effects it had - separately from the aborted work.



// This will be used to quickly determine if a subtree has no pending changes.
pendingWorkPriority: PriorityLevel,

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down
Loading