Skip to content

Commit

Permalink
[Fiber] Add top level render callbacks into ReactDOMFiber and ReactNo…
Browse files Browse the repository at this point in the history
…op (#8102)

* [Fiber] Add top level render callbacks into ReactDOMFiber and ReactNoop

* [Fiber] Support multiple render callbacks

* [Fiber] `this` in render callbacks are public instances

* [Fiber] commitLifeCycles move to behind the effectTag check
  • Loading branch information
koba04 authored and sebmarkbage committed Oct 26, 2016
1 parent 2ba571c commit 2ef1208
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 13 deletions.
6 changes: 3 additions & 3 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ function warnAboutUnstableUse() {

var ReactDOM = {

render(element : ReactElement<any>, container : DOMContainerElement) {
render(element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
warnAboutUnstableUse();
let root;
if (!container._reactRootContainer) {
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container);
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer);
DOMRenderer.updateContainer(element, root = container._reactRootContainer, callback);
}
return DOMRenderer.getPublicRootInstance(root);
},
Expand Down
20 changes: 20 additions & 0 deletions src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ describe('ReactDOMFiber', () => {
expect(container.textContent).toEqual('10');
});

it('should be called a callback argument', () => {
// mounting phase
let called = false;
ReactDOM.render(
<div>Foo</div>,
container,
() => called = true
);
expect(called).toEqual(true);

// updating phase
called = false;
ReactDOM.render(
<div>Foo</div>,
container,
() => called = true
);
expect(called).toEqual(true);
});

if (ReactDOMFeatureFlags.useFiber) {
it('should render a component returning strings directly from render', () => {
const Text = ({value}) => value;
Expand Down
6 changes: 3 additions & 3 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ var ReactNoop = {

root: rootContainer,

render(element : ReactElement<any>) {
render(element : ReactElement<any>, callback: ?Function) {
if (!root) {
root = NoopRenderer.mountContainer(element, rootContainer);
root = NoopRenderer.mountContainer(element, rootContainer, callback);
} else {
NoopRenderer.updateContainer(element, root);
NoopRenderer.updateContainer(element, root, callback);
}
},

Expand Down
8 changes: 8 additions & 0 deletions src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
attachRef(current, finishedWork, instance);
return;
}
case HostContainer: {
const instance = finishedWork.stateNode;
if (instance.callbackList) {
const { callbackList } = instance;
instance.callbackList = null;
callCallbacks(callbackList, instance.current.child.stateNode);
}
}
case HostComponent: {
const instance : I = finishedWork.stateNode;
attachRef(current, finishedWork, instance);
Expand Down
18 changes: 16 additions & 2 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');

var { createUpdateQueue, addCallbackToQueue } = require('ReactFiberUpdateQueue');

if (__DEV__) {
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
}
Expand Down Expand Up @@ -79,9 +81,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :

return {

mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode {
mountContainer(element : ReactElement<any>, containerInfo : C, callback: ?Function) : OpaqueNode {
const root = createFiberRoot(containerInfo);
const container = root.current;
if (callback) {
const queue = createUpdateQueue(null);
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
// TODO: Use pending work/state instead of props.
// TODO: This should not override the pendingWorkPriority if there is
// higher priority work in the subtree.
Expand All @@ -99,9 +106,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
return container;
},

updateContainer(element : ReactElement<any>, container : OpaqueNode) : void {
updateContainer(element : ReactElement<any>, container : OpaqueNode, callback: ?Function) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
if (callback) {
const queue = root.callbackList ?
root.callbackList :
createUpdateQueue(null);
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
// TODO: Use pending work/state instead of props.
root.current.pendingProps = element;

Expand Down
4 changes: 4 additions & 0 deletions src/renderers/shared/fiber/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'use strict';

import type { Fiber } from 'ReactFiber';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';

const { createHostContainerFiber } = require('ReactFiber');

Expand All @@ -25,6 +26,8 @@ export type FiberRoot = {
isScheduled: boolean,
// The work schedule is a linked list.
nextScheduledRoot: ?FiberRoot,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
};

exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
Expand All @@ -36,6 +39,7 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
containerInfo: containerInfo,
isScheduled: false,
nextScheduledRoot: null,
callbackList: null,
};
uninitializedFiber.stateNode = root;
return root;
Expand Down
1 change: 1 addition & 0 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
if (finishedWork.effectTag !== NoEffect) {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
commitLifeCycles(current, finishedWork);
}
}

Expand Down
15 changes: 10 additions & 5 deletions src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('ReactIncremental', () => {

it('should render a simple component, in steps if needed', () => {

var renderCallbackCalled = false;
var barCalled = false;
function Bar() {
barCalled = true;
Expand All @@ -52,17 +53,20 @@ describe('ReactIncremental', () => {
];
}

ReactNoop.render(<Foo />);
ReactNoop.render(<Foo />, () => renderCallbackCalled = true);
expect(fooCalled).toBe(false);
expect(barCalled).toBe(false);
expect(renderCallbackCalled).toBe(false);
// Do one step of work.
ReactNoop.flushDeferredPri(7 + 5);
expect(fooCalled).toBe(true);
expect(barCalled).toBe(false);
expect(renderCallbackCalled).toBe(false);
// Do the rest of the work.
ReactNoop.flushDeferredPri(50);
expect(fooCalled).toBe(true);
expect(barCalled).toBe(true);
expect(renderCallbackCalled).toBe(true);
});

it('updates a previous render', () => {
Expand Down Expand Up @@ -98,21 +102,22 @@ describe('ReactIncremental', () => {
);
}

ReactNoop.render(<Foo text="foo" />);
ReactNoop.render(<Foo text="foo" />, () => ops.push('renderCallbackCalled'));
ReactNoop.flush();

expect(ops).toEqual(['Foo', 'Header', 'Content', 'Footer']);
expect(ops).toEqual(['Foo', 'Header', 'Content', 'Footer', 'renderCallbackCalled']);

ops = [];

ReactNoop.render(<Foo text="bar" />);
ReactNoop.render(<Foo text="bar" />, () => ops.push('firstRenderCallbackCalled'));
ReactNoop.render(<Foo text="bar" />, () => ops.push('secondRenderCallbackCalled'));
ReactNoop.flush();

// TODO: Test bail out of host components. This is currently unobservable.

// Since this is an update, it should bail out and reuse the work from
// Header and Content.
expect(ops).toEqual(['Foo', 'Content']);
expect(ops).toEqual(['Foo', 'Content', 'firstRenderCallbackCalled', 'secondRenderCallbackCalled']);

});

Expand Down

0 comments on commit 2ef1208

Please sign in to comment.