mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Initial implementation of new-style refs
cf. #1373 This implementation can be used in any situation that refs can currently be used (and can also be used without an owner, which is a plus).
This commit is contained in:
@@ -28,6 +28,7 @@ var ReactMount = require('ReactMount');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactPropTypes = require('ReactPropTypes');
|
||||
var ReactRef = require('ReactRef');
|
||||
var ReactServerRendering = require('ReactServerRendering');
|
||||
var ReactTextComponent = require('ReactTextComponent');
|
||||
|
||||
@@ -62,6 +63,9 @@ var React = {
|
||||
createClass: ReactClass.createClass,
|
||||
createElement: createElement,
|
||||
createFactory: createFactory,
|
||||
createRef: function() {
|
||||
return new ReactRef();
|
||||
},
|
||||
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
|
||||
constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
|
||||
render: render,
|
||||
|
||||
+21
-10
@@ -13,6 +13,7 @@
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
var ReactRef = require('ReactRef');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
@@ -56,6 +57,22 @@ var unmountIDFromEnvironment = null;
|
||||
*/
|
||||
var mountImageIntoNode = null;
|
||||
|
||||
function attachRef(ref, component, owner) {
|
||||
if (ref instanceof ReactRef) {
|
||||
ReactRef.attachRef(ref, component);
|
||||
} else {
|
||||
ReactOwner.addComponentAsRefTo(component, ref, owner);
|
||||
}
|
||||
}
|
||||
|
||||
function detachRef(ref, component, owner) {
|
||||
if (ref instanceof ReactRef) {
|
||||
ReactRef.detachRef(ref, component);
|
||||
} else {
|
||||
ReactOwner.removeComponentAsRefFrom(component, ref, owner);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Components are the basic units of composition in React.
|
||||
*
|
||||
@@ -255,7 +272,7 @@ var ReactComponent = {
|
||||
var ref = this._currentElement.ref;
|
||||
if (ref != null) {
|
||||
var owner = this._currentElement._owner;
|
||||
ReactOwner.addComponentAsRefTo(this, ref, owner);
|
||||
attachRef(ref, this, owner);
|
||||
}
|
||||
this._rootNodeID = rootID;
|
||||
this._lifeCycleState = ComponentLifeCycle.MOUNTED;
|
||||
@@ -280,7 +297,7 @@ var ReactComponent = {
|
||||
);
|
||||
var ref = this._currentElement.ref;
|
||||
if (ref != null) {
|
||||
ReactOwner.removeComponentAsRefFrom(this, ref, this._owner);
|
||||
detachRef(ref, this, this._owner);
|
||||
}
|
||||
unmountIDFromEnvironment(this._rootNodeID);
|
||||
this._rootNodeID = null;
|
||||
@@ -350,17 +367,11 @@ var ReactComponent = {
|
||||
if (nextElement._owner !== prevElement._owner ||
|
||||
nextElement.ref !== prevElement.ref) {
|
||||
if (prevElement.ref != null) {
|
||||
ReactOwner.removeComponentAsRefFrom(
|
||||
this, prevElement.ref, prevElement._owner
|
||||
);
|
||||
detachRef(prevElement.ref, this, prevElement._owner);
|
||||
}
|
||||
// Correct, even if the owner is the same, and only the ref has changed.
|
||||
if (nextElement.ref != null) {
|
||||
ReactOwner.addComponentAsRefTo(
|
||||
this,
|
||||
nextElement.ref,
|
||||
nextElement._owner
|
||||
);
|
||||
attachRef(nextElement.ref, this, nextElement._owner);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactRef
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ReactRef.attachRef = function(ref, value) {
|
||||
ref._value = value;
|
||||
};
|
||||
|
||||
ReactRef.detachRef = function(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;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactRef;
|
||||
@@ -118,6 +118,114 @@ describe('ReactComponent', function() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
});
|
||||
|
||||
it('should support new-style refs', function() {
|
||||
var innerObj = {}, outerObj = {};
|
||||
|
||||
var Wrapper = React.createClass({
|
||||
getObject: function() {
|
||||
return this.props.object;
|
||||
},
|
||||
render: function() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
});
|
||||
|
||||
var refsResolved = 0;
|
||||
var refsErrored = 0;
|
||||
var Component = React.createClass({
|
||||
componentWillMount: function() {
|
||||
this.innerRef = React.createRef();
|
||||
this.outerRef = React.createRef();
|
||||
this.unusedRef = React.createRef();
|
||||
},
|
||||
render: function() {
|
||||
var inner = <Wrapper object={innerObj} ref={this.innerRef} />;
|
||||
var outer = (
|
||||
<Wrapper object={outerObj} ref={this.outerRef}>
|
||||
{inner}
|
||||
</Wrapper>
|
||||
);
|
||||
return outer;
|
||||
},
|
||||
componentDidMount: function() {
|
||||
// TODO: Currently new refs aren't available on initial render
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
this.innerRef.then(function(inner) {
|
||||
expect(inner.getObject()).toEqual(innerObj);
|
||||
refsResolved++;
|
||||
});
|
||||
this.outerRef.then(function(outer) {
|
||||
expect(outer.getObject()).toEqual(outerObj);
|
||||
refsResolved++;
|
||||
});
|
||||
this.unusedRef.then(function() {
|
||||
throw new Error("Unused ref should not be resolved");
|
||||
}, function() {
|
||||
refsErrored++;
|
||||
});
|
||||
expect(refsResolved).toBe(0);
|
||||
expect(refsErrored).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
var instance = <Component />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
instance.forceUpdate();
|
||||
expect(refsResolved).toBe(2);
|
||||
expect(refsErrored).toBe(1);
|
||||
});
|
||||
|
||||
it('should support new-style refs with mixed-up owners', function() {
|
||||
var Wrapper = React.createClass({
|
||||
render: function() {
|
||||
return this.props.getContent();
|
||||
}
|
||||
});
|
||||
|
||||
var refsResolved = 0;
|
||||
var Component = React.createClass({
|
||||
componentWillMount: function() {
|
||||
this.wrapperRef = React.createRef();
|
||||
this.innerRef = React.createRef();
|
||||
},
|
||||
getInner: function() {
|
||||
// (With old-style refs, it's impossible to get a ref to this div
|
||||
// because Wrapper is the current owner when this function is called.)
|
||||
return <div title="inner" ref={this.innerRef} />;
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Wrapper
|
||||
title="wrapper"
|
||||
ref={this.wrapperRef}
|
||||
getContent={this.getInner}
|
||||
/>
|
||||
);
|
||||
},
|
||||
componentDidMount: function() {
|
||||
// TODO: Currently new refs aren't available on initial render
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
// Check .props.title to make sure we got the right elements back
|
||||
this.wrapperRef.then(function(wrapper) {
|
||||
expect(wrapper.props.title).toBe("wrapper");
|
||||
refsResolved++;
|
||||
});
|
||||
this.innerRef.then(function(inner) {
|
||||
expect(inner.props.title).toEqual("inner");
|
||||
refsResolved++;
|
||||
});
|
||||
expect(refsResolved).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
var instance = <Component />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
instance.forceUpdate();
|
||||
expect(refsResolved).toBe(2);
|
||||
});
|
||||
|
||||
it('should correctly determine if a component is mounted', function() {
|
||||
var Component = React.createClass({
|
||||
componentWillMount: function() {
|
||||
|
||||
Reference in New Issue
Block a user