Currently, `ReactUpdates` updates dirty components in increasing order of mount depth. However, mount depth is only relative to the component passed into `React.render`. This breaks down for components that invoke `React.render` as an implementation detail because the child components will be updated before the parent component.

This fixes the problem by using the order in which components are mounted (instead of their depth). The mount order transcends component trees (rooted at `React.render` calls).

Reviewers: @sebmarkbage @zpao

Test Plan:
Ran unit tests successfully:

```
npm run jest
```
This commit is contained in:
yungsters 2014-11-04 14:25:50 -08:00
parent 3a0f30480d
commit c7fd626b1f
3 changed files with 51 additions and 4 deletions

View File

@ -99,6 +99,14 @@ var CompositeLifeCycle = keyMirror({
RECEIVING_PROPS: null
});
/**
* An incrementing ID assigned to each component when it is mounted. This is
* used to enforce the order in which `ReactUpdates` updates dirty components.
*
* @private
*/
var nextMountID = 1;
/**
* @lends {ReactCompositeComponent.prototype}
*/
@ -132,6 +140,7 @@ var ReactCompositeComponentMixin = assign({},
ReactComponent.Mixin.construct.apply(this, arguments);
this._context = null;
this._mountOrder = 0;
// See ReactUpdates.
this._pendingCallbacks = null;
@ -167,6 +176,7 @@ var ReactCompositeComponentMixin = assign({},
);
this._context = context;
this._mountOrder = nextMountID++;
this._rootNodeID = rootID;
var inst = this._instance;

View File

@ -110,14 +110,14 @@ function batchedUpdates(callback, a, b) {
}
/**
* Array comparator for ReactComponents by owner depth
* Array comparator for ReactComponents by mount ordering.
*
* @param {ReactComponent} c1 first component you're comparing
* @param {ReactComponent} c2 second component you're comparing
* @return {number} Return value usable by Array.prototype.sort().
*/
function mountDepthComparator(c1, c2) {
return c1._mountDepth - c2._mountDepth;
function mountOrderComparator(c1, c2) {
return c1._mountOrder - c2._mountOrder;
}
function runBatchedUpdates(transaction) {
@ -133,7 +133,7 @@ function runBatchedUpdates(transaction) {
// Since reconciling a component higher in the owner hierarchy usually (not
// always -- see shouldComponentUpdate()) will reconcile children, reconcile
// them before their children by sorting the array.
dirtyComponents.sort(mountDepthComparator);
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
// If a component is unmounted before pending changes apply, it will still

View File

@ -629,6 +629,43 @@ describe('ReactUpdates', function() {
]);
});
it('should flush updates in the correct order across roots', function() {
var instances = [];
var updates = [];
var MockComponent = React.createClass({
render: function() {
updates.push(this.props.depth);
return <div />;
},
componentDidMount: function() {
instances.push(this);
if (this.props.depth < this.props.count) {
React.renderComponent(
<MockComponent
depth={this.props.depth + 1}
count={this.props.count}
/>,
this.getDOMNode()
);
}
}
});
ReactTestUtils.renderIntoDocument(<MockComponent depth={0} count={2} />);
expect(updates).toEqual([0, 1, 2]);
ReactUpdates.batchedUpdates(function() {
// Simulate update on each component from top to bottom.
instances.forEach(function(instance) {
instance.forceUpdate();
});
});
expect(updates).toEqual([0, 1, 2, 0, 1, 2]);
});
it('should queue nested updates', function() {
// See https://github.com/facebook/react/issues/1147