React Fiber Implementation Notes
I've been trying to wrap my head around Fiber's source code, so I figured I'd share my notes. Will hopefully organize this better once I get a better idea of how everything fits together.
To do this, I'm working with a really simple sample app (shown below) and tracing through Fiber. I've marked up React's source to help with this. I'm sure there's a better way to do this and I'd love suggestions.
To follow along, clone React:
git clone https://github.com/facebook/react.git
The Fiber source code can be found at react/src/renderers/shared/fiber
.
ReactDOMFiber can be found at react/src/renderers/dom/fiber
.
Sample Step-Through
With the following app code:
const TextBox = ({ onChange }) => (
<input onChange={ onChange }></input>
);
const Display = ({ text }) => (
<div>{ text }</div>
)
class App extends Component {
constructor() {
super();
this.state = {
text: 'type',
};
};
changeText = (event) => {
const value = event.target.value;
this.setState((state, props) => ({
text: value
}));
};
render() {
return (
<div>
<Display text={ this.state.text }/>
<TextBox onChange = { this.changeText } />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
Trace the initial mounting of the app:
-
call
ReactDOM.render()
, which calls -
ReactDOM.renderSubtreeIntoContainer()
, which calls -
ReactFiberReconciler.updateContainer()
.- This begins the traversal by calling
scheduleTopLevelUpdate()
.
- This begins the traversal by calling
-
scheduleTopLevelUpdate()
kicks off the excitement with the following calls:
ReactDOM.render(<App />, <div id="root">...</div>)
Arguments
- element:
{
type: function App()
//...
}
- container:
<div id="root">...</div>
- callback:
undefined
Execution
renderSubtreeIntoContainer(null, element, container, callback);
ReactDOM.renderSubtreeIntoContainer(null, element, container, callback)
Arguments
- parentComponent:
null
- children:
//this is the element retruned from <App />'s render().
{
type: function App(),
//...
}
- containerNode:
<div id="root"></div>
- callback:
undefined
Execution
First, set container: DOMContainerElement
based on containerNode.nodeType
.
This is a safety check - if containerNode
is a DOCUMENT_NODE
, we get the root element of the document.
Our container, <div id="root" />
is an ELEMENT_NODE
, so we just assign container = containerNode
.
let container: DOMContainerElement = containerNode.nodeType === DOCUMENT_NODE
? (containerNode: any).documentElement
: (containerNode: any);
Then set root
equal to container._reactRootContainer
. In our case, root === undefined
, so we enter the following conditional block.
In the block, we prepare our root
for mounting in two steps:
- clear existing content from the container.
- Create a container with
DOMRenderer.createContainer(container)
Then, call DOMRenderer.updateContainer()
with our root
. Note that we do not batch the update if this is our initial mount, indicated by a nonexistent container._reactRootContainer
.
This DOMRenderer.updateContainer()
call will reconcile and mount the whole tree (!!!)
let root = container._reactRootContainer;
if (!root) {
// First clear any existing content.
while (container.lastChild) {
container.removeChild(container.lastChild);
}
const newRoot = DOMRenderer.createContainer(container);
root = container._reactRootContainer = newRoot;
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
});
} else {
DOMRenderer.updateContainer(children, root, parentComponent, callback);
}
ReactFiberReconciler.updateContainer(children, root)
Arguments
- element:
//this is the element retruned from <App />'s render().
{
type: function App(),
//...
}
- container:
{
context: null,
pendingContext: null,
current: [object Object],
//...
}
- updateContainer() also accepts
parentComponent
andcallback
arguments, but they are bothnull
here.
Execution
- call
getContextForSubtree(parentComponent)
. In this case, it returns an empty object. - assign
context
tocontainer.context
. - call
scheduleTopLevelUpdate(current, element)
//parentComponent is null here. getContextForSubtree() returns an empty object.
const context = getContextForSubtree(parentComponent)
//container.context === null
if(container.context === null) container.context = context;
else container.pendingContext = context;
//container.current is the container's current Fiber.
scheduleTopLevelUpdate(container.current, element);
ReactFiberReconciler.scheduleTopLevelUpdate() for <App />
Arguments
- current
//<App />'s fiber. This is passed to the next call - we do not use any of its properties here.
{
//...
}
- element
//this is the element retruned from <App />'s render().
{
type: function App(),
//...
}
scheduleTopLevelUpdate()
also accepts acallback
argument, but it is null here.
Execution
- Retrieve the fiber's priority level for scheduling.
- In the real source, this also checks if the element is an async wrapper component. If so, it will return
LowPriority
.
- In the real source, this also checks if the element is an async wrapper component. If so, it will return
- Call
ReactFiberUpdateQueue.addTopLevelUpdate()
- Call
ReactFiberScheduler.scheduleUpdate()
const priorityLevel = getPriorityContext(current);
const nextState = { element };
addTopLevelUpdate(current, nextState, callback, priorityLevel);
scheduleUpdate(current, priorityLevel);
ReactFiberUpdateQueue.addTopLevelUpdate() for <App />
Arguments
- fiber
- partialState
{
//this is the element retruned from <App />'s render().
element: {
type: function App(),
//...
}
}
- priorityLevel
1
addTopLevelUpdate()
also accepts acallback
argument, but it isnull
here.
Execution
- Generate an
update
object and callinsertUpdate
:
const update = {
priorityLevel,
partialState,
callback,
isReplace: false,
isForced: false,
isTopLevelUnmount,
next: null,
};
insertUpdate(fiber, update);