mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Reviewed By: kentaromiura Differential Revision: D4026114 fbshipit-source-id: 67808af91454d95941fea01eef58a4d9086f46e1
222 lines
7.0 KiB
JavaScript
222 lines
7.0 KiB
JavaScript
/**
|
|
* Copyright 2013-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule traverseAllChildren
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
|
|
var REACT_ELEMENT_TYPE = require('ReactElementSymbol');
|
|
|
|
var getIteratorFn = require('getIteratorFn');
|
|
var invariant = require('fbjs/lib/invariant');
|
|
var KeyEscapeUtils = require('KeyEscapeUtils');
|
|
var warning = require('fbjs/lib/warning');
|
|
|
|
var SEPARATOR = '.';
|
|
var SUBSEPARATOR = ':';
|
|
|
|
/**
|
|
* This is inlined from ReactElement since this file is shared between
|
|
* isomorphic and renderers. We could extract this to a
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* TODO: Test that a single child and an array with one item have the same key
|
|
* pattern.
|
|
*/
|
|
|
|
var didWarnAboutMaps = false;
|
|
|
|
/**
|
|
* Generate a key string that identifies a component within a set.
|
|
*
|
|
* @param {*} component A component that could contain a manual key.
|
|
* @param {number} index Index that is used if a manual key is not provided.
|
|
* @return {string}
|
|
*/
|
|
function getComponentKey(component, index) {
|
|
// Do some typechecking here since we call this blindly. We want to ensure
|
|
// that we don't block potential future ES APIs.
|
|
if (component && typeof component === 'object' && component.key != null) {
|
|
// Explicit key
|
|
return KeyEscapeUtils.escape(component.key);
|
|
}
|
|
// Implicit key determined by the index in the set
|
|
return index.toString(36);
|
|
}
|
|
|
|
/**
|
|
* @param {?*} children Children tree container.
|
|
* @param {!string} nameSoFar Name of the key path so far.
|
|
* @param {!function} callback Callback to invoke with each child found.
|
|
* @param {?*} traverseContext Used to pass information throughout the traversal
|
|
* process.
|
|
* @return {!number} The number of children in this subtree.
|
|
*/
|
|
function traverseAllChildrenImpl(
|
|
children,
|
|
nameSoFar,
|
|
callback,
|
|
traverseContext
|
|
) {
|
|
var type = typeof children;
|
|
|
|
if (type === 'undefined' || type === 'boolean') {
|
|
// All of the above are perceived as null.
|
|
children = null;
|
|
}
|
|
|
|
if (children === null ||
|
|
type === 'string' ||
|
|
type === 'number' ||
|
|
// The following is inlined from ReactElement. This means we can optimize
|
|
// some checks. React Fiber also inlines this logic for similar purposes.
|
|
(type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)) {
|
|
callback(
|
|
traverseContext,
|
|
children,
|
|
// If it's the only child, treat the name as if it was wrapped in an array
|
|
// so that it's consistent if the number of children grows.
|
|
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
var child;
|
|
var nextName;
|
|
var subtreeCount = 0; // Count of children found in the current subtree.
|
|
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
|
|
|
|
if (Array.isArray(children)) {
|
|
for (var i = 0; i < children.length; i++) {
|
|
child = children[i];
|
|
nextName = nextNamePrefix + getComponentKey(child, i);
|
|
subtreeCount += traverseAllChildrenImpl(
|
|
child,
|
|
nextName,
|
|
callback,
|
|
traverseContext
|
|
);
|
|
}
|
|
} else {
|
|
var iteratorFn = getIteratorFn(children);
|
|
if (iteratorFn) {
|
|
var iterator = iteratorFn.call(children);
|
|
var step;
|
|
if (iteratorFn !== children.entries) {
|
|
var ii = 0;
|
|
while (!(step = iterator.next()).done) {
|
|
child = step.value;
|
|
nextName = nextNamePrefix + getComponentKey(child, ii++);
|
|
subtreeCount += traverseAllChildrenImpl(
|
|
child,
|
|
nextName,
|
|
callback,
|
|
traverseContext
|
|
);
|
|
}
|
|
} else {
|
|
if (__DEV__) {
|
|
var mapsAsChildrenAddendum = '';
|
|
if (ReactCurrentOwner.current) {
|
|
var mapsAsChildrenOwnerName = ReactCurrentOwner.current.getName();
|
|
if (mapsAsChildrenOwnerName) {
|
|
mapsAsChildrenAddendum = ' Check the render method of `' + mapsAsChildrenOwnerName + '`.';
|
|
}
|
|
}
|
|
warning(
|
|
didWarnAboutMaps,
|
|
'Using Maps as children is not yet fully supported. It is an ' +
|
|
'experimental feature that might be removed. Convert it to a ' +
|
|
'sequence / iterable of keyed ReactElements instead.%s',
|
|
mapsAsChildrenAddendum
|
|
);
|
|
didWarnAboutMaps = true;
|
|
}
|
|
// Iterator will provide entry [k,v] tuples rather than values.
|
|
while (!(step = iterator.next()).done) {
|
|
var entry = step.value;
|
|
if (entry) {
|
|
child = entry[1];
|
|
nextName = (
|
|
nextNamePrefix +
|
|
KeyEscapeUtils.escape(entry[0]) + SUBSEPARATOR +
|
|
getComponentKey(child, 0)
|
|
);
|
|
subtreeCount += traverseAllChildrenImpl(
|
|
child,
|
|
nextName,
|
|
callback,
|
|
traverseContext
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else if (type === 'object') {
|
|
var addendum = '';
|
|
if (__DEV__) {
|
|
addendum =
|
|
' If you meant to render a collection of children, use an array ' +
|
|
'instead or wrap the object using createFragment(object) from the ' +
|
|
'React add-ons.';
|
|
if (children._isReactElement) {
|
|
addendum =
|
|
' It looks like you\'re using an element created by a different ' +
|
|
'version of React. Make sure to use only one copy of React.';
|
|
}
|
|
if (ReactCurrentOwner.current) {
|
|
var name = ReactCurrentOwner.current.getName();
|
|
if (name) {
|
|
addendum += ' Check the render method of `' + name + '`.';
|
|
}
|
|
}
|
|
}
|
|
var childrenString = String(children);
|
|
invariant(
|
|
false,
|
|
'Objects are not valid as a React child (found: %s).%s',
|
|
childrenString === '[object Object]' ?
|
|
'object with keys {' + Object.keys(children).join(', ') + '}' :
|
|
childrenString,
|
|
addendum
|
|
);
|
|
}
|
|
}
|
|
|
|
return subtreeCount;
|
|
}
|
|
|
|
/**
|
|
* Traverses children that are typically specified as `props.children`, but
|
|
* might also be specified through attributes:
|
|
*
|
|
* - `traverseAllChildren(this.props.children, ...)`
|
|
* - `traverseAllChildren(this.props.leftPanelChildren, ...)`
|
|
*
|
|
* The `traverseContext` is an optional argument that is passed through the
|
|
* entire traversal. It can be used to store accumulations or anything else that
|
|
* the callback might find relevant.
|
|
*
|
|
* @param {?*} children Children tree object.
|
|
* @param {!function} callback To invoke upon traversing each child.
|
|
* @param {?*} traverseContext Context for traversal.
|
|
* @return {!number} The number of children in this subtree.
|
|
*/
|
|
function traverseAllChildren(children, callback, traverseContext) {
|
|
if (children == null) {
|
|
return 0;
|
|
}
|
|
|
|
return traverseAllChildrenImpl(children, '', callback, traverseContext);
|
|
}
|
|
|
|
module.exports = traverseAllChildren;
|