Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 39 additions & 19 deletions src/core/ReactCompositeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,6 @@ var ReactCompositeComponentMixin = assign({},
context
);

ReactRef.attachRefs(this, this._currentElement);

this._context = context;
this._mountOrder = nextMountID++;
this._rootNodeID = rootID;
Expand Down Expand Up @@ -254,9 +252,18 @@ var ReactCompositeComponentMixin = assign({},
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
transaction.getReactMountReady().enqueue(this.attachRefs, this);
return markup;
},

/**
* Helper to call ReactRef.attachRefs with this composite component, split out
* to avoid allocations in the transaction mount-ready queue.
*/
attachRefs: function() {
ReactRef.attachRefs(this, this._currentElement);
},

/**
* Releases any resources allocated by `mountComponent`.
*
Expand All @@ -266,6 +273,8 @@ var ReactCompositeComponentMixin = assign({},
unmountComponent: function() {
var inst = this._instance;

ReactRef.detachRefs(this, this._currentElement);

this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
if (inst.componentWillUnmount) {
inst.componentWillUnmount();
Expand All @@ -281,8 +290,6 @@ var ReactCompositeComponentMixin = assign({},
this._pendingCallbacks = null;
this._pendingElement = null;

ReactRef.detachRefs(this, this._currentElement);

ReactComponent.Mixin.unmountComponent.call(this);

ReactComponentEnvironment.unmountIDFromEnvironment(this._rootNodeID);
Expand Down Expand Up @@ -728,15 +735,23 @@ var ReactCompositeComponentMixin = assign({},
nextUnmaskedContext
);

// Update refs regardless of what shouldComponentUpdate returns
ReactRef.updateRefs(this, prevParentElement, nextParentElement);

var inst = this._instance;

var prevContext = inst.context;
var prevProps = inst.props;
var nextContext = prevContext;
var nextProps = prevProps;

var refsChanged = ReactRef.shouldUpdateRefs(
this,
prevParentElement,
nextParentElement
);

if (refsChanged) {
ReactRef.detachRefs(this, prevParentElement);
}

// Distinguish between a props update versus a simple state update
if (prevParentElement !== nextParentElement) {
nextContext = this._processContext(nextParentElement._context);
Expand Down Expand Up @@ -774,27 +789,31 @@ var ReactCompositeComponentMixin = assign({},
}
}

if (!shouldUpdate) {
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext
);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
return;
}

this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext
);
// Update refs regardless of what shouldComponentUpdate returns
if (refsChanged) {
transaction.getReactMountReady().enqueue(this.attachRefs, this);
}
},

/**
Expand All @@ -819,6 +838,7 @@ var ReactCompositeComponentMixin = assign({},
) {
var inst = this._instance;

var prevElement = this._currentElement;
var prevProps = inst.props;
var prevState = inst.state;
var prevContext = inst.context;
Expand Down
106 changes: 12 additions & 94 deletions src/core/ReactRef.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,98 +14,22 @@
var ReactOwner = require('ReactOwner');
var ReactUpdates = require('ReactUpdates');

var accumulate = require('accumulate');
var assign = require('Object.assign');
var forEachAccumulated = require('forEachAccumulated');
var invariant = require('invariant');

function ReactRef() {
this._value = null;
this._successCallbacks = null;
this._failureCallbacks = null;
}

/**
* Call the enqueued success or failure callbacks for a ref, as appropriate.
*/
function dispatchCallbacks() {
/*jshint validthis:true */
var successCallbacks = this._successCallbacks;
var failureCallbacks = this._failureCallbacks;
this._successCallbacks = null;
this._failureCallbacks = null;

if (this._value) {
forEachAccumulated(successCallbacks, callSuccess, this);
} else {
forEachAccumulated(failureCallbacks, callFailure);
}
}

/**
* Call a single success callback, passing the ref's value.
*/
function callSuccess(cb) {
/*jshint validthis:true */
cb(this._value);
}

/**
* Call a single failure callback, passing no arguments.
*/
function callFailure(cb) {
cb();
}

assign(ReactRef.prototype, {
/**
* Get the value of a ref asynchronously. Accepts a success callback and an
* optional failure callback. If the ref has been rendered, the success
* callback will be called with the component instance; otherwise, the failure
* callback will be executed.
*
* @param {function} success Callback in case of success
* @param {?function} failure Callback in case of failure
*/
then: function(success, failure) {
invariant(
typeof success === 'function',
'ReactRef.then(...): Must provide a success callback.'
);
if (this._successCallbacks == null) {
ReactUpdates.asap(dispatchCallbacks, this);
}
this._successCallbacks = accumulate(this._successCallbacks, success);
if (failure) {
this._failureCallbacks = accumulate(this._failureCallbacks, failure);
}
}
});

function attachFirstClassRef(ref, value) {
ref._value = value.getPublicInstance();
}

function detachFirstClassRef(ref, value) {
// Check that `component` is still the current ref because we do not want to
// detach the ref if another component stole it.
if (ref._value === value) {
ref._value = null;
}
}
var ReactRef = {};

function attachRef(ref, component, owner) {
if (ref instanceof ReactRef) {
attachFirstClassRef(ref, component);
if (typeof ref === 'function') {
ref(component.getPublicInstance());
} else {
// Legacy ref
ReactOwner.addComponentAsRefTo(component, ref, owner);
}
}

function detachRef(ref, component, owner) {
if (ref instanceof ReactRef) {
detachFirstClassRef(ref, component);
if (typeof ref === 'function') {
ref(null);
} else {
// Legacy ref
ReactOwner.removeComponentAsRefFrom(component, ref, owner);
}
}
Expand All @@ -117,7 +41,7 @@ ReactRef.attachRefs = function(instance, element) {
}
};

ReactRef.updateRefs = function(instance, prevElement, nextElement) {
ReactRef.shouldUpdateRefs = function(instance, prevElement, nextElement) {
// If either the owner or a `ref` has changed, make sure the newest owner
// has stored a reference to `this`, and the previous owner (if different)
// has forgotten the reference to `this`. We use the element instead
Expand All @@ -130,16 +54,10 @@ ReactRef.updateRefs = function(instance, prevElement, nextElement) {
// is made. It probably belongs where the key checking and
// instantiateReactComponent is done.

if (nextElement._owner !== prevElement._owner ||
nextElement.ref !== prevElement.ref) {
if (prevElement.ref != null) {
detachRef(prevElement.ref, instance, prevElement._owner);
}
// Correct, even if the owner is the same, and only the ref has changed.
if (nextElement.ref != null) {
attachRef(nextElement.ref, instance, nextElement._owner);
}
}
return (
nextElement._owner !== prevElement._owner ||
nextElement.ref !== prevElement.ref
);
};

ReactRef.detachRefs = function(instance, element) {
Expand Down
Loading