-
Notifications
You must be signed in to change notification settings - Fork 51k
[Fiber] Host Side Effects #7154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f84a8ea
a4b8beb
62d4561
2f0ff6e
c6b5622
05c6925
e60fb7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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> |
| 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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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."?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
|
||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None yet, sorry.