ReactChildren
Instead of changing `traverseAllChildren`, keep that around for perf reasons (for the hot code path `flattenChildren`) Introduce `ReactChildren.map` and `ReactChildren.forEach` which mirrors `Array.prototype.map` and `Array.prototype.forEach`. This involves a rename of `mapAllChildren`
This commit is contained in:
parent
0321171113
commit
9ef4e74ba2
|
@ -18,6 +18,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var ReactChildren = require('ReactChildren');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
|
@ -39,6 +40,8 @@ var React = {
|
|||
createClass: ReactCompositeComponent.createClass,
|
||||
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
|
||||
constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
|
||||
forEachChildren: ReactChildren.forEach,
|
||||
mapChildren: ReactChildren.map,
|
||||
renderComponent: ReactMount.renderComponent,
|
||||
renderComponentToString: ReactServerRendering.renderComponentToString,
|
||||
unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule mapAllChildren
|
||||
* @providesModule ReactChildren
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
@ -23,8 +23,50 @@ var PooledClass = require('PooledClass');
|
|||
var invariant = require('invariant');
|
||||
var traverseAllChildren = require('traverseAllChildren');
|
||||
|
||||
var twoArgumentPooler = PooledClass.twoArgumentPooler;
|
||||
var threeArgumentPooler = PooledClass.threeArgumentPooler;
|
||||
|
||||
/**
|
||||
* PooledClass representing the bookkeeping associated with performing a child
|
||||
* traversal. Allows avoiding binding callbacks.
|
||||
*
|
||||
* @constructor ForEachBookKeeping
|
||||
* @param {!function} forEachFunction Function to perform traversal with.
|
||||
* @param {?*} forEachContext Context to perform context with.
|
||||
*/
|
||||
function ForEachBookKeeping(forEachFunction, forEachContext) {
|
||||
this.forEachFunction = forEachFunction;
|
||||
this.forEachContext = forEachContext;
|
||||
}
|
||||
PooledClass.addPoolingTo(ForEachBookKeeping, twoArgumentPooler);
|
||||
|
||||
function forEachSingleChild(traverseContext, child, name, i) {
|
||||
var forEachBookKeeping = traverseContext;
|
||||
forEachBookKeeping.forEachFunction.call(
|
||||
forEachBookKeeping.forEachContext, child, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through children that are typically specified as `props.children`.
|
||||
*
|
||||
* The provided forEachFunc(child, index) will be called for each
|
||||
* leaf child.
|
||||
*
|
||||
* @param {array} children
|
||||
* @param {function(*, int)} forEachFunc.
|
||||
* @param {*} forEachContext Context for forEachContext.
|
||||
*/
|
||||
function forEachChildren(children, forEachFunc, forEachContext) {
|
||||
if (children == null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
var traverseContext =
|
||||
ForEachBookKeeping.getPooled(forEachFunc, forEachContext);
|
||||
traverseAllChildren(children, forEachSingleChild, traverseContext);
|
||||
ForEachBookKeeping.release(traverseContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* PooledClass representing the bookkeeping associated with performing a child
|
||||
* mapping. Allows avoiding binding callbacks.
|
||||
|
@ -41,20 +83,16 @@ function MapBookKeeping(mapResult, mapFunction, mapContext) {
|
|||
}
|
||||
PooledClass.addPoolingTo(MapBookKeeping, threeArgumentPooler);
|
||||
|
||||
module.exports = MapBookKeeping;
|
||||
|
||||
|
||||
function mapSingleChildIntoContext(traverseContext, child, name, i) {
|
||||
var mapBookKeeping = traverseContext;
|
||||
var mapResult = mapBookKeeping.mapResult;
|
||||
var mapFunction = mapBookKeeping.mapFunction;
|
||||
var mapContext = mapBookKeeping.mapContext;
|
||||
var mappedChild = mapFunction.call(mapContext, child, name, i);
|
||||
// We found a component instance.
|
||||
var mappedChild =
|
||||
mapBookKeeping.mapFunction.call(mapBookKeeping.mapContext, child, i);
|
||||
// We found a component instance
|
||||
invariant(
|
||||
!mapResult.hasOwnProperty(name),
|
||||
'mapAllChildren(...): Encountered two children with the same key, `%s`. ' +
|
||||
'Children keys must be unique.',
|
||||
'ReactChildren.map(...): Encountered two children with the same key, ' +
|
||||
'`%s`. Children keys must be unique.',
|
||||
name
|
||||
);
|
||||
mapResult[name] = mappedChild;
|
||||
|
@ -66,25 +104,29 @@ function mapSingleChildIntoContext(traverseContext, child, name, i) {
|
|||
* The provided mapFunction(child, key, index) will be called for each
|
||||
* leaf child.
|
||||
*
|
||||
* TODO: This may likely break any calls to `mapAllChildren` that were
|
||||
* TODO: This may likely break any calls to `ReactChildren.map` that were
|
||||
* previously relying on the fact that we guarded against null children.
|
||||
*
|
||||
* @param {array} children
|
||||
* @param {function(*, string, int)} mapFunction.
|
||||
* @param {function(*, int)} mapFunction.
|
||||
* @param {*} mapContext Context for mapFunction.
|
||||
* @return {array} mirrored array with mapped children.
|
||||
*/
|
||||
function mapAllChildren(children, mapFunction, mapContext) {
|
||||
function mapChildren(children, func, context) {
|
||||
if (children == null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
var mapResult = {};
|
||||
var traverseContext =
|
||||
MapBookKeeping.getPooled(mapResult, mapFunction, mapContext);
|
||||
var traverseContext = MapBookKeeping.getPooled(mapResult, func, context);
|
||||
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
|
||||
MapBookKeeping.release(traverseContext);
|
||||
return mapResult;
|
||||
}
|
||||
|
||||
module.exports = mapAllChildren;
|
||||
var ReactChildren = {
|
||||
forEach: forEachChildren,
|
||||
map: mapChildren
|
||||
};
|
||||
|
||||
module.exports = ReactChildren;
|
|
@ -19,18 +19,18 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
describe('mapAllChildren', function() {
|
||||
var mapAllChildren;
|
||||
describe('ReactChildren', function() {
|
||||
var ReactChildren;
|
||||
var React;
|
||||
|
||||
beforeEach(function() {
|
||||
mapAllChildren = require('mapAllChildren');
|
||||
ReactChildren = require('ReactChildren');
|
||||
React = require('React');
|
||||
});
|
||||
|
||||
|
||||
it('should support identity for simple', function() {
|
||||
var mapFn = jasmine.createSpy().andCallFake(function (kid, key, index) {
|
||||
var callback = jasmine.createSpy().andCallFake(function (kid, index) {
|
||||
return kid;
|
||||
});
|
||||
|
||||
|
@ -40,44 +40,53 @@ describe('mapAllChildren', function() {
|
|||
// using structures that arrive from transforms.
|
||||
|
||||
var instance = <div>{simpleKid}</div>;
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
expect(mapFn).toHaveBeenCalledWith(simpleKid, '[simple]', 0);
|
||||
ReactChildren.forEach(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
callback.reset();
|
||||
var mappedChildren = ReactChildren.map(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
expect(mappedChildren[Object.keys(mappedChildren)[0]]).toBe(simpleKid);
|
||||
});
|
||||
|
||||
it('should treat single arrayless child as being in array', function() {
|
||||
var mapFn = jasmine.createSpy().andCallFake(function (kid, key, index) {
|
||||
var callback = jasmine.createSpy().andCallFake(function (kid, index) {
|
||||
return kid;
|
||||
});
|
||||
|
||||
var simpleKid = <span />;
|
||||
var instance = <div>{simpleKid}</div>;
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
expect(mapFn).toHaveBeenCalledWith(simpleKid, '[0]', 0);
|
||||
ReactChildren.forEach(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
callback.reset();
|
||||
var mappedChildren = ReactChildren.map(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
expect(mappedChildren[Object.keys(mappedChildren)[0]]).toBe(simpleKid);
|
||||
});
|
||||
|
||||
it('should treat single child in array as expected', function() {
|
||||
var mapFn = jasmine.createSpy().andCallFake(function (kid, key, index) {
|
||||
var callback = jasmine.createSpy().andCallFake(function (kid, index) {
|
||||
return kid;
|
||||
});
|
||||
|
||||
var simpleKid = <span />;
|
||||
var instance = <div>{[simpleKid]}</div>;
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
expect(mapFn).toHaveBeenCalledWith(simpleKid, '[0]', 0);
|
||||
ReactChildren.forEach(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
callback.reset();
|
||||
var mappedChildren = ReactChildren.map(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(simpleKid, 0);
|
||||
expect(mappedChildren[Object.keys(mappedChildren)[0]]).toBe(simpleKid);
|
||||
});
|
||||
|
||||
it('should pass key to returned component', function() {
|
||||
var mapFn = function (kid, key, index) {
|
||||
var mapFn = function (kid, index) {
|
||||
return <div>{kid}</div>;
|
||||
};
|
||||
|
||||
var simpleKid = <span key="simple" />;
|
||||
|
||||
var instance = <div>{simpleKid}</div>;
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
var mappedChildren = ReactChildren.map(instance.props.children, mapFn);
|
||||
|
||||
var mappedKeys = Object.keys(mappedChildren);
|
||||
expect(mappedKeys.length).toBe(1);
|
||||
|
@ -87,7 +96,9 @@ describe('mapAllChildren', function() {
|
|||
});
|
||||
|
||||
it('should invoke callback with the right context', function() {
|
||||
var mapFn = function (kid, key, index) {
|
||||
var lastContext;
|
||||
var callback = function (kid, index) {
|
||||
lastContext = this;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -95,8 +106,11 @@ describe('mapAllChildren', function() {
|
|||
|
||||
var simpleKid = <span key="simple" />;
|
||||
var instance = <div>{simpleKid}</div>;
|
||||
ReactChildren.forEach(instance.props.children, callback, scopeTester);
|
||||
expect(lastContext).toBe(scopeTester);
|
||||
|
||||
var mappedChildren =
|
||||
mapAllChildren(instance.props.children, mapFn, scopeTester);
|
||||
ReactChildren.map(instance.props.children, callback, scopeTester);
|
||||
|
||||
var mappedKeys = Object.keys(mappedChildren);
|
||||
expect(mappedKeys.length).toBe(1);
|
||||
|
@ -116,7 +130,7 @@ describe('mapAllChildren', function() {
|
|||
var threeMapped = <span />; // Map from null to something.
|
||||
var fourMapped = <div key="keyFour" />;
|
||||
|
||||
var mapFn = jasmine.createSpy().andCallFake(function (kid, key, index) {
|
||||
var callback = jasmine.createSpy().andCallFake(function (kid, index) {
|
||||
return index === 0 ? zeroMapped :
|
||||
index === 1 ? oneMapped :
|
||||
index === 2 ? twoMapped :
|
||||
|
@ -133,28 +147,37 @@ describe('mapAllChildren', function() {
|
|||
</div>
|
||||
);
|
||||
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
ReactChildren.forEach(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(zero, 0);
|
||||
expect(callback).toHaveBeenCalledWith(one, 1);
|
||||
expect(callback).toHaveBeenCalledWith(two, 2);
|
||||
expect(callback).toHaveBeenCalledWith(three, 3);
|
||||
expect(callback).toHaveBeenCalledWith(four, 4);
|
||||
callback.reset();
|
||||
|
||||
var mappedChildren =
|
||||
ReactChildren.map(instance.props.children, callback);
|
||||
var mappedKeys = Object.keys(mappedChildren);
|
||||
expect(mapFn.calls.length).toBe(5);
|
||||
expect(callback.calls.length).toBe(5);
|
||||
expect(mappedKeys.length).toBe(5);
|
||||
// Keys default to indices.
|
||||
expect(mappedKeys).toEqual(
|
||||
['[keyZero]', '[1]', '[keyTwo]', '[3]', '[keyFour]']
|
||||
);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(zero, '[keyZero]', 0);
|
||||
expect(callback).toHaveBeenCalledWith(zero, 0);
|
||||
expect(mappedChildren[mappedKeys[0]]).toBe(zeroMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(one, '[1]', 1);
|
||||
expect(callback).toHaveBeenCalledWith(one, 1);
|
||||
expect(mappedChildren[mappedKeys[1]]).toBe(oneMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(two, '[keyTwo]', 2);
|
||||
expect(callback).toHaveBeenCalledWith(two, 2);
|
||||
expect(mappedChildren[mappedKeys[2]]).toBe(twoMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(three, '[3]', 3);
|
||||
expect(callback).toHaveBeenCalledWith(three, 3);
|
||||
expect(mappedChildren[mappedKeys[3]]).toBe(threeMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(four, '[keyFour]', 4);
|
||||
expect(callback).toHaveBeenCalledWith(four, 4);
|
||||
expect(mappedChildren[mappedKeys[4]]).toBe(fourMapped);
|
||||
});
|
||||
|
||||
|
@ -180,7 +203,7 @@ describe('mapAllChildren', function() {
|
|||
var fourMapped = <div key="keyFour" />;
|
||||
var fiveMapped = <div />;
|
||||
|
||||
var mapFn = jasmine.createSpy().andCallFake(function (kid, key, index) {
|
||||
var callback = jasmine.createSpy().andCallFake(function (kid, index) {
|
||||
return index === 0 ? zeroMapped :
|
||||
index === 1 ? oneMapped :
|
||||
index === 2 ? twoMapped :
|
||||
|
@ -198,9 +221,18 @@ describe('mapAllChildren', function() {
|
|||
}</div>
|
||||
);
|
||||
|
||||
var mappedChildren = mapAllChildren(instance.props.children, mapFn);
|
||||
ReactChildren.forEach(instance.props.children, callback);
|
||||
expect(callback).toHaveBeenCalledWith(zero, 0);
|
||||
expect(callback).toHaveBeenCalledWith(one, 1);
|
||||
expect(callback).toHaveBeenCalledWith(two, 2);
|
||||
expect(callback).toHaveBeenCalledWith(three, 3);
|
||||
expect(callback).toHaveBeenCalledWith(four, 4);
|
||||
expect(callback).toHaveBeenCalledWith(five, 5);
|
||||
callback.reset();
|
||||
|
||||
var mappedChildren = ReactChildren.map(instance.props.children, callback);
|
||||
var mappedKeys = Object.keys(mappedChildren);
|
||||
expect(mapFn.calls.length).toBe(6);
|
||||
expect(callback.calls.length).toBe(6);
|
||||
expect(mappedKeys.length).toBe(6);
|
||||
// Keys default to indices.
|
||||
expect(mappedKeys).toEqual([
|
||||
|
@ -212,22 +244,22 @@ describe('mapAllChildren', function() {
|
|||
'[0]{keyFive}'
|
||||
]);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(zero, '[0]{firstHalfKey}[keyZero]', 0);
|
||||
expect(callback).toHaveBeenCalledWith(zero, 0);
|
||||
expect(mappedChildren[mappedKeys[0]]).toBe(zeroMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(one, '[0]{firstHalfKey}[1]', 1);
|
||||
expect(callback).toHaveBeenCalledWith(one, 1);
|
||||
expect(mappedChildren[mappedKeys[1]]).toBe(oneMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(two, '[0]{firstHalfKey}[keyTwo]', 2);
|
||||
expect(callback).toHaveBeenCalledWith(two, 2);
|
||||
expect(mappedChildren[mappedKeys[2]]).toBe(twoMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(three, '[0]{secondHalfKey}[0]', 3);
|
||||
expect(callback).toHaveBeenCalledWith(three, 3);
|
||||
expect(mappedChildren[mappedKeys[3]]).toBe(threeMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(four, '[0]{secondHalfKey}[keyFour]', 4);
|
||||
expect(callback).toHaveBeenCalledWith(four, 4);
|
||||
expect(mappedChildren[mappedKeys[4]]).toBe(fourMapped);
|
||||
|
||||
expect(mapFn).toHaveBeenCalledWith(five, '[0]{keyFive}', 5);
|
||||
expect(callback).toHaveBeenCalledWith(five, 5);
|
||||
expect(mappedChildren[mappedKeys[5]]).toBe(fiveMapped);
|
||||
});
|
||||
|
||||
|
@ -240,7 +272,7 @@ describe('mapAllChildren', function() {
|
|||
// Key should be added even if we don't supply it!
|
||||
var oneForceKeyMapped = <div />;
|
||||
|
||||
var mapFn = function(kid, key, index) {
|
||||
var mapFn = function(kid, index) {
|
||||
return index === 0 ? zeroForceKeyMapped : oneForceKeyMapped;
|
||||
};
|
||||
|
||||
|
@ -253,13 +285,13 @@ describe('mapAllChildren', function() {
|
|||
|
||||
var expectedForcedKeys = ['[keyZero]', '[keyOne]'];
|
||||
var mappedChildrenForcedKeys =
|
||||
mapAllChildren(forcedKeys.props.children, mapFn);
|
||||
ReactChildren.map(forcedKeys.props.children, mapFn);
|
||||
var mappedForcedKeys = Object.keys(mappedChildrenForcedKeys);
|
||||
expect(mappedForcedKeys).toEqual(expectedForcedKeys);
|
||||
|
||||
var expectedRemappedForcedKeys = ['{[keyZero]}', '{[keyOne]}'];
|
||||
var remappedChildrenForcedKeys =
|
||||
mapAllChildren(mappedChildrenForcedKeys, mapFn);
|
||||
ReactChildren.map(mappedChildrenForcedKeys, mapFn);
|
||||
expect(
|
||||
Object.keys(remappedChildrenForcedKeys)
|
||||
).toEqual(expectedRemappedForcedKeys);
|
||||
|
@ -282,7 +314,7 @@ describe('mapAllChildren', function() {
|
|||
);
|
||||
|
||||
expect(function() {
|
||||
mapAllChildren(instance.props.children, mapFn);
|
||||
ReactChildren.map(instance.props.children, mapFn);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
|
@ -296,7 +328,7 @@ describe('mapAllChildren', function() {
|
|||
);
|
||||
|
||||
expect(function() {
|
||||
mapAllChildren(instance.props.children, mapFn);
|
||||
ReactChildren.map(instance.props.children, mapFn);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue