Skip to content

Commit

Permalink
Switch first-class refs to use functions
Browse files Browse the repository at this point in the history
Closes facebook#1373.

Test Plan: jest
  • Loading branch information
sophiebits committed Jan 23, 2015
1 parent 734aedb commit 322bde6
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 164 deletions.
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

0 comments on commit 322bde6

Please sign in to comment.