Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d9393ee
Add Fragment fiber type
sebmarkbage Sep 13, 2016
e8c1cb4
Add Text node types
sebmarkbage Sep 14, 2016
d7322d8
Silence Fiber warning when the feature flag is on
sebmarkbage Sep 13, 2016
e05ab67
Fix MultiChild tests so they work with Fiber
sebmarkbage Sep 13, 2016
8c7409b
Add comment about bug in yields
sebmarkbage Sep 15, 2016
4ed67f1
Enable text updates in ReactNoop
sebmarkbage Sep 19, 2016
dff0faf
Fiber child reconciliation
sebmarkbage Sep 20, 2016
51f2bf9
Add index field to each fiber
sebmarkbage Sep 21, 2016
bcbceae
Don't track side-effects unless needed
sebmarkbage Sep 21, 2016
c49a91c
Fast path for create child
sebmarkbage Oct 4, 2016
be0acf8
Deletion tracking
sebmarkbage Oct 4, 2016
76725fe
Tag the fiber with the kind of side-effect that was applied to it
sebmarkbage Oct 4, 2016
86e854e
Append deletions to the effect list
sebmarkbage Oct 4, 2016
eabed69
Move child updates to use the reconciled effects
sebmarkbage Oct 5, 2016
31ca1e7
Remove beginWork shortcut
sebmarkbage Oct 6, 2016
0262e70
Reset effect list when we recompute children
sebmarkbage Oct 7, 2016
f3d7116
Always override priority level when visiting children
sebmarkbage Oct 7, 2016
40989f8
Call componentWillUnmount during deletion phase
sebmarkbage Oct 7, 2016
8360088
Fire componentDidMount/componentDidUpdate life-cycles
sebmarkbage Oct 7, 2016
48cf81c
Resolve ref callbacks
sebmarkbage Oct 7, 2016
8721ec1
Invoke all null ref calls before any new ref calls
sebmarkbage Oct 7, 2016
d9efde7
Fix resuming bug
sebmarkbage Oct 10, 2016
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
Prev Previous commit
Next Next commit
Add Text node types
These nodes rendering into Text nodes in the DOM.

There is a special case when a string is a direct child of a host
node. In that case, we won't reconcile it as a child fiber. In
terms of fibers, it is terminal. However, the host config special
cases it.

It is kind of unfortunate that we have to special case this kind
of child in the HostConfig. It would be nice to unify this with
other types of child instances. Text nodes have some weird special
cases, but those special cases tend to *vary* by environment.
They're not the same special cases so not sure how valuable it is
to have a special protocol and special types for it.
  • Loading branch information
sebmarkbage committed Oct 17, 2016
commit e8c1cb4b693f58fbc0320e92c2baeab9ffe35f12
29 changes: 20 additions & 9 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ type DOMContainerElement = Element & { _reactRootContainer: ?Object };
type Container = Element;
type Props = { };
type Instance = Element;
type TextInstance = Text;

function recursivelyAppendChildren(parent : Element, child : HostChildren<Instance>) {
function recursivelyAppendChildren(parent : Element, child : HostChildren<Instance | TextInstance>) {
if (!child) {
return;
}
/* $FlowFixMe: Element should have this property. */
if (child.nodeType === 1) {
/* $FlowFixMe: Element and Text should have this property. */
if (child.nodeType === 1 || child.nodeType === 3) {
/* $FlowFixMe: Refinement issue. I don't know how to express different. */
parent.appendChild(child);
} else {
Expand All @@ -43,15 +44,16 @@ function recursivelyAppendChildren(parent : Element, child : HostChildren<Instan

var DOMRenderer = ReactFiberReconciler({

updateContainer(container : Container, children : HostChildren<Instance>) : void {
updateContainer(container : Container, children : HostChildren<Instance | TextInstance>) : void {
container.innerHTML = '';
recursivelyAppendChildren(container, children);
},

createInstance(type : string, props : Props, children : HostChildren<Instance>) : Instance {
createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
const domElement = document.createElement(type);
recursivelyAppendChildren(domElement, children);
if (typeof props.children === 'string') {
if (typeof props.children === 'string' ||
typeof props.children === 'number') {
domElement.textContent = props.children;
}
return domElement;
Expand All @@ -61,15 +63,16 @@ var DOMRenderer = ReactFiberReconciler({
domElement : Instance,
oldProps : Props,
newProps : Props,
children : HostChildren<Instance>
children : HostChildren<Instance | TextInstance>
) : boolean {
return true;
},

commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : void {
commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance | TextInstance>) : void {
domElement.innerHTML = '';
recursivelyAppendChildren(domElement, children);
if (typeof newProps.children === 'string') {
if (typeof newProps.children === 'string' ||
typeof newProps.children === 'number') {
domElement.textContent = newProps.children;
}
},
Expand All @@ -78,6 +81,14 @@ var DOMRenderer = ReactFiberReconciler({
// Noop
},

createTextInstance(text : string) : TextInstance {
return document.createTextNode(text);
},

commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
textInstance.nodeValue = newText;
},

scheduleAnimationCallback: window.requestAnimationFrame,

scheduleDeferredCallback: window.requestIdleCallback,
Expand Down
37 changes: 25 additions & 12 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,18 @@ var scheduledDeferredCallback = null;

const TERMINAL_TAG = 99;

type Container = { rootID: number, children: Array<Instance> };
type Container = { rootID: number, children: Array<Instance | TextInstance> };
type Props = { prop: any };
type Instance = { tag: 99, type: string, id: number, children: Array<Instance>, prop: any };
type Instance = { tag: 99, type: string, id: number, children: Array<Instance | TextInstance>, prop: any };
type TextInstance = string;

var instanceCounter = 0;

function recursivelyAppendChildren(flatArray : Array<Instance>, child : HostChildren<Instance>) {
function recursivelyAppendChildren(flatArray : Array<Instance | TextInstance>, child : HostChildren<Instance | TextInstance>) {
if (!child) {
return;
}
if (child.tag === TERMINAL_TAG) {
if (typeof child === 'string' || child.tag === TERMINAL_TAG) {
flatArray.push(child);
} else {
let node = child;
Expand All @@ -53,19 +54,19 @@ function recursivelyAppendChildren(flatArray : Array<Instance>, child : HostChil
}
}

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

var NoopRenderer = ReactFiberReconciler({

updateContainer(containerInfo : Container, children : HostChildren<Instance>) : void {
updateContainer(containerInfo : Container, children : HostChildren<Instance | TextInstance>) : void {
containerInfo.children = flattenChildren(children);
},

createInstance(type : string, props : Props, children : HostChildren<Instance>) : Instance {
createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
const inst = {
tag: TERMINAL_TAG,
id: instanceCounter++,
Expand All @@ -79,18 +80,26 @@ var NoopRenderer = ReactFiberReconciler({
return inst;
},

prepareUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : boolean {
prepareUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance | TextInstance>) : boolean {
return true;
},

commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : void {
commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance | TextInstance>) : void {
instance.children = flattenChildren(children);
instance.prop = newProps.prop;
},

deleteInstance(instance : Instance) : void {
},

createTextInstance(text : string) : TextInstance {
return text;
},

commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
// Not yet supported.
},

scheduleAnimationCallback(callback) {
scheduledAnimationCallback = callback;
},
Expand Down Expand Up @@ -166,11 +175,15 @@ var ReactNoop = {
bufferedLog.push(...args, '\n');
}

function logHostInstances(children: Array<Instance>, depth) {
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
logHostInstances(child.children, depth + 1);
if (typeof child === 'string') {
log(' '.repeat(depth) + '- ' + child);
} else {
log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
logHostInstances(child.children, depth + 1);
}
}
}
function logContainer(container : Container, depth) {
Expand Down
14 changes: 14 additions & 0 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const {
cloneFiber,
createFiberFromElement,
createFiberFromFragment,
createFiberFromText,
createFiberFromCoroutine,
createFiberFromYield,
} = ReactFiber;
Expand All @@ -50,6 +51,13 @@ function ChildReconciler(shouldClone) {
newChildren : any,
priority : PriorityLevel
) : Fiber {
if (typeof newChildren === 'string') {
const textNode = createFiberFromText(newChildren, priority);
previousSibling.sibling = textNode;
textNode.return = returnFiber;
return textNode;
}

if (typeof newChildren !== 'object' || newChildren === null) {
return previousSibling;
}
Expand Down Expand Up @@ -111,6 +119,12 @@ function ChildReconciler(shouldClone) {
}

function createFirstChild(returnFiber, existingChild, newChildren : any, priority) {
if (typeof newChildren === 'string') {
const textNode = createFiberFromText(newChildren, priority);
textNode.return = returnFiber;
return textNode;
}

if (typeof newChildren !== 'object' || newChildren === null) {
return null;
}
Expand Down
8 changes: 8 additions & 0 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var {
ClassComponent,
HostContainer,
HostComponent,
HostText,
CoroutineComponent,
YieldComponent,
Fragment,
Expand Down Expand Up @@ -256,6 +257,13 @@ exports.createFiberFromFragment = function(elements : ReactFragment, priorityLev
return fiber;
};

exports.createFiberFromText = function(content : string, priorityLevel : PriorityLevel) {
const fiber = createFiber(HostText, null);
fiber.pendingProps = content;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};

function createFiberFromElementType(type : mixed, key : null | string) {
let fiber;
if (typeof type === 'function') {
Expand Down
16 changes: 14 additions & 2 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var {
ClassComponent,
HostContainer,
HostComponent,
HostText,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
Expand All @@ -50,7 +51,7 @@ var {
} = require('ReactFiberUpdateQueue');
var ReactInstanceMap = require('ReactInstanceMap');

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, getScheduler : () => Scheduler) {

function markChildAsProgressed(current, workInProgress, priorityLevel) {
// We now have clones. Let's store them as the currently progressed work.
Expand Down Expand Up @@ -239,7 +240,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
}

function updateHostComponent(current, workInProgress) {
const nextChildren = workInProgress.pendingProps.children;
let nextChildren = workInProgress.pendingProps.children;
if (typeof nextChildren === 'string' || typeof nextChildren === 'number') {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also have access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
}
if (workInProgress.pendingProps.hidden &&
workInProgress.pendingWorkPriority !== OffscreenPriority) {
// If this host component is hidden, we can bail out on the children.
Expand Down Expand Up @@ -391,6 +399,10 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
config.beginUpdate(workInProgress.stateNode);
}
return updateHostComponent(current, workInProgress);
case HostText:
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
return null;
case CoroutineHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
workInProgress.tag = CoroutineComponent;
Expand Down
16 changes: 15 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ var {
ClassComponent,
HostContainer,
HostComponent,
HostText,
} = ReactTypeOfWork;
var { callCallbacks } = require('ReactFiberUpdateQueue');

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {

const updateContainer = config.updateContainer;
const commitUpdate = config.commitUpdate;
const commitTextUpdate = config.commitTextUpdate;

function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
Expand Down Expand Up @@ -68,6 +70,18 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
commitUpdate(instance, oldProps, newProps, children);
return;
}
case HostText: {
if (finishedWork.stateNode == null || !current) {
throw new Error('This should only be done during updates.');
}
// TODO: This never gets called yet because I don't have update support
// for text nodes. This only gets updated through a host component or
// container updating with this as one of its child nodes.
const textInstance : TI = finishedWork.stateNode;
const oldText : string = finishedWork.memoizedProps;
const newText : string = current.memoizedProps;
commitTextUpdate(textInstance, oldText, newText);
}
default:
throw new Error('This unit of work tag should not have side-effects.');
}
Expand Down
26 changes: 25 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ var {
ClassComponent,
HostContainer,
HostComponent,
HostText,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
Fragment,
} = ReactTypeOfWork;

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {

const createInstance = config.createInstance;
const createTextInstance = config.createTextInstance;
const prepareUpdate = config.prepareUpdate;

function markForPreEffect(workInProgress : Fiber) {
Expand Down Expand Up @@ -190,6 +192,28 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
}
workInProgress.memoizedProps = newProps;
return null;
case HostText:
let newText = workInProgress.pendingProps;
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.
markForPreEffect(workInProgress);
} else {
if (typeof newText !== 'string') {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
const textInstance = createTextInstance(newText);
// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = textInstance;
workInProgress.output = textInstance;
}
workInProgress.memoizedProps = newText;
return null;
case CoroutineComponent:
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
Expand Down
15 changes: 9 additions & 6 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,22 @@ type HostChildNode<I> = { tag: TypeOfWork, output: HostChildren<I>, sibling: any

export type HostChildren<I> = null | void | I | HostChildNode<I>;

export type HostConfig<T, P, I, C> = {
export type HostConfig<T, P, I, TI, C> = {

// 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<I>) : void;
updateContainer(containerInfo : C, children : HostChildren<I | TI>) : void;

createInstance(type : T, props : P, children : HostChildren<I>) : I,
prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : boolean,
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : void,
createInstance(type : T, props : P, children : HostChildren<I | TI>) : I,
prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I | TI>) : boolean,
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I | TI>) : void,
deleteInstance(instance : I) : void,

createTextInstance(text : string) : TI,
commitTextUpdate(textInstance : TI, oldText : string, newText : string) : void,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

An alternative approach would overload the existing createInstance and commitUpdate to accept a string and return a Text node. This might seem like it causes unnecessary branching trouble there, but if you think about it, all other instances tend to be more specific types than "View" or "Element". They're all subclasses so you need to branch on them sooner or later anyway.

There isn't necessarily any need to special case Text components here.

It is needed in the internals for reconciliation purposes since string children are special. However, they're not needed here.

You could argue that since they're already special cased by having their own fiber, it allows early branching instead of branching in the switch statement and then branching again inside the host environment adapter.


scheduleAnimationCallback(callback : () => void) : void,
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void

Expand All @@ -58,7 +61,7 @@ export type Reconciler<C> = {
getPublicRootInstance(container : OpaqueNode) : (C | null),
};

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconciler<C> {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C> {

var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);

Expand Down
2 changes: 1 addition & 1 deletion src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type Scheduler = {
scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void
};

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
// Use a closure to circumvent the circular dependency between the scheduler
// and ReactFiberBeginWork. Don't know if there's a better way to do this.
let scheduler;
Expand Down
Loading