You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
6.7 KiB
173 lines
6.7 KiB
/** |
|
* Copyright (c) 2013-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var _prodInvariant = require('./reactProdInvariant'); |
|
|
|
var ReactCurrentOwner = require('./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 (process.env.NODE_ENV !== 'production') { |
|
var mapsAsChildrenAddendum = ''; |
|
if (ReactCurrentOwner.current) { |
|
var mapsAsChildrenOwnerName = ReactCurrentOwner.current.getName(); |
|
if (mapsAsChildrenOwnerName) { |
|
mapsAsChildrenAddendum = ' Check the render method of `' + mapsAsChildrenOwnerName + '`.'; |
|
} |
|
} |
|
process.env.NODE_ENV !== 'production' ? 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) : void 0; |
|
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 (process.env.NODE_ENV !== 'production') { |
|
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); |
|
!false ? process.env.NODE_ENV !== 'production' ? 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) : _prodInvariant('31', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum) : void 0; |
|
} |
|
} |
|
|
|
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; |