[Fiber] Add top level render callbacks into ReactDOMFiber and ReactNoop (#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
This commit is contained in:
Toru Kobayashi
2016-10-27 02:19:19 +09:00
committed by Sebastian Markbåge
parent 2ba571c246
commit 2ef12084e4
8 changed files with 65 additions and 13 deletions
+3 -3
View File
@@ -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);
},
@@ -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;
+3 -3
View File
@@ -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);
}
},
@@ -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);
@@ -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');
}
@@ -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.
@@ -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;
@@ -13,6 +13,7 @@
'use strict';
import type { Fiber } from 'ReactFiber';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
const { createHostContainerFiber } = require('ReactFiber');
@@ -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 {
@@ -36,6 +39,7 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
containerInfo: containerInfo,
isScheduled: false,
nextScheduledRoot: null,
callbackList: null,
};
uninitializedFiber.stateNode = root;
return root;
@@ -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);
}
}
@@ -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;
@@ -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', () => {
@@ -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']);
});