共计 11005 个字符,预计需要花费 28 分钟才能阅读完成。
React 源码阅读 -12
ReactChildren
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// invariant ,warning 用户报错警告
import invariant from 'shared/invariant';
import warning from 'shared/warning';
// 引入 react Symbol 对象类型标识
import {
getIteratorFn,
REACT_ELEMENT_TYPE,
REACT_PORTAL_TYPE,
} from 'shared/ReactSymbols';
// isValidElement 检测是否是一个 reactelement 对象
//cloneAndReplaceKey 复制一个 React 元素并替换的留属性:key 值。import {isValidElement, cloneAndReplaceKey} from './ReactElement';
//react 当前调试信息
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
const SEPARATOR = '.';
const SUBSEPARATOR = ':';
/**
* Escape and wrap key so it is safe to use as a reactid
*
* @param {string} key to be escaped.
* @return {string} the escaped key.
*/
// 定义和转换 key
// 将传入的 key 中所有的 '=' 替换成 '=0',':' 替换成 '=2', 并在 key 之前加上 '$'
function escape(key) {const escapeRegex = /[=:]/g;
const escaperLookup = {
'=': '=0',
':': '=2',
};
const escapedString = ('' + key).replace(escapeRegex, function(match) {return escaperLookup[match];
});
return '$' + escapedString;
}
/**
* TODO: Test that a single child and an array with one item have the same key
* pattern.
*/
let didWarnAboutMaps = false;
const userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text) {
// / 如果字符串中有连续多个 / 的话,在匹配的字串后再加 /
// react 对 key 定义的一个规则:// 如果字符串中有连续多个 / 的话,在匹配的字串后再加 /
// 这个函数一般是第二层递归时,会用到
// 匹配一个或者多个 "/", 并用 '$&/' 替换
return (''+ text).replace(userProvidedKeyEscapeRegex,'$&/');
}
const POOL_SIZE = 10;
const traverseContextPool = [];
// 实际上维护了一个 size 为 10 的缓冲池.
// 如果 pool 中有存货, 则 pop 出一个进行使用.
// 如果 pool 中空空如也, 则 return 一个新的对象.
function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
// func: 这就是用户传入的 forEach 处理函数
// context: 这是个可选参数, 用户可以传入作为调用上述 func 时的上下文.
// 看到这里你就知道, 默认情况下,
// 你的 处理函数执行的时候, 是没有 context, 也就是处理函数中,this === undefined.
// 如果想在 处理函数中绑定 this, 只能通过这个参数指定.
// 这一点在后面分析 forEachSingleChild 会看到原理
if (traverseContextPool.length) {// pop() 方法用于删除并返回数组的最后一个元素。const traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
} else {
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0,
};
}
}
function releaseTraverseContext(traverseContext) {
// 这个方法简单的不能再简单,
// 核心目的就是 if 里面的那块代码,
// 如果池数量小于 POOL_SIZE(上文中得知这个数字是 10),
// 则把对象放回到池中, 以备后续使用.
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {traverseContextPool.push(traverseContext);
}
}
/**
* @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,
) {
const type = typeof children;
// 这个分支处理了大部分类型. 这里的 children 实际上是单个对象, 并不是像它的名字一样是个复数.
// 接下来执行 callback(traverseContext, children, nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar)
// 并返回 1.
// 看到这里, 我们就知道, children 不仅仅可以是 Component,
// 还可以是 String/Boolean/Undefined 等等.
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
let invokeCallback = false;
if (children === null) {invokeCallback = true;} else {switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
if (invokeCallback) {
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.
// 这样一来,如果元素数量增加,这是一致的 s
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
// 这个 callback 实际上是 forEachSingleChild
// 而 forEachSingleChild 则调用 func.call(context, child, ...)
// 实现了绑定上下文调用处理函数. 所以 callback(traverseContext, children, ...)
// 可以简单理解为以 children(实际上是单个对象, 并不是集合, 名字容易误导读者) 为参数, 调用了用户的处理函数.
);
return 1;
}
let child;
let nextName;
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
if (Array.isArray(children)) {
// !! 如果是 Array, 则深度递归 !!
for (let i = 0; i < children.length; i++) {child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else {
// !! 如果不是 Array, 则看该对象是否可迭代 !!
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {if (__DEV__) {
// Warn about using Maps as children
if (iteratorFn === children.entries) {
warning(
didWarnAboutMaps,
'Using Maps as children is unsupported and will likely yield' +
'unexpected results. Convert it to a sequence/iterable of keyed' +
'ReactElements instead.',
);
didWarnAboutMaps = true;
}
}
const iterator = iteratorFn.call(children);
let step;
let ii = 0;
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getComponentKey(child, ii++);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else if (type === 'object') {
// !! 如果该对象不可迭代, 则提示错误 !!
let addendum = '';
if (__DEV__) {
addendum =
'If you meant to render a collection of children, use an array' +
'instead.' +
ReactDebugCurrentFrame.getStackAddendum();}
const childrenString = '' + 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,
);
}
}
// React.Children.forEach 是不支持 Map 但却支持 Set 的. 看代码中的注释,
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;}
// traverseAllChildren 只是个入口, 真实的递归遍历是定义在 traverseAllChildrenImpl 中:
// 递归遍历
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
/**
* 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.
// 所以在这里做一些类型检查。我们要确保
// / 不会阻止潜在的未来 ES API。if (
typeof component === 'object' &&
component !== null &&
component.key != null
) {
// Explicit key
// 组件 key
return escape(component.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
// 转换成 36 进制
}
function forEachSingleChild(bookKeeping, child, name) {const {func, context} = bookKeeping;
func.call(context, child, bookKeeping.count++);
}
/**
* Iterates through children that are typically specified as `props.children`.
*
* See https://reactjs.org/docs/react-api.html#reactchildrenforeach
*
* The provided forEachFunc(child, index) will be called for each
* leaf child.
*
* @param {?*} children Children tree container.
* @param {function(*, int)} forEachFunc
* @param {*} forEachContext Context for forEachContext.
*/
function forEachChildren(children, forEachFunc, forEachContext) {if (children == null) {return children;}
const traverseContext = getPooledTraverseContext(
null,
null,
forEachFunc,
forEachContext,
);
// 其中, children 是要遍历的子节点对象,
// traverseContext 是上一步封装了 处理函数 和 处理函数执行上下文 的一个对象.
// 而 forEachSingleChild 则是真正调用处理函数的方法:
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}
function mapSingleChildIntoContext(bookKeeping, child, childKey) {const {result, keyPrefix, func, context} = bookKeeping;
let mappedChild = func.call(context, child, bookKeeping.count++);
if (Array.isArray(mappedChild)) {mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if (mappedChild != null) {if (isValidElement(mappedChild)) {
mappedChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: '') +
childKey,
);
}
result.push(mappedChild);
}
}
// getPooledTraverseContext()/traverseAllChildren()/releaseTraverseContext()
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
// 对 prefix 进行处理
// 如果字符串中有连续多个 / 的话,在匹配的字串后再加 /
let escapedPrefix = '';
if (prefix != null) {escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
// 遍历上下文
//traverseContext=
// {// result:[],
// keyPrefix:'',
// func:(item)=>{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
// 遍历所有的 Children 将嵌套的数组展平 绑定上下文调用处理函数
// 其中, children 是要遍历的子节点对象,
// traverseContext 是上一步封装了 处理函数
// 和 处理函数执行上下文 的一个对象.
// 而 mapSingleChildIntoContext 则是真正调用处理函数的方法:
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
// 这个方法简单的不能再简单,
// 核心目的就是 if 里面的那块代码,
// 如果池数量小于 POOL_SIZE(上文中得知这个数字是 10),
// 则把对象放回到池中, 以备后续使用.
releaseTraverseContext(traverseContext);
}
/**
* Maps children that are typically specified as `props.children`.
* 映射通常指定为 `props.children` 的子级
* See https://reactjs.org/docs/react-api.html#reactchildrenmap
*
* The provided mapFunction(child, key, index) will be called for each
* leaf child.
*
* @param {?*} children Children tree container.
* @param {function(*, int)} func The map function.
* @param {*} context Context for mapFunction.
* @return {object} Object containing the ordered map of results.
*/
function mapChildren(children, func, context) {if (children == null) {return children;}
const result = [];
// 将 children 完全遍历,遍历的节点最终全部存到 array 中,是 ReactElement 的节点会更改 key 之后再放到 array 中。mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
/**
* Count the number of children that are typically specified as
* `props.children`.
*
* See https://reactjs.org/docs/react-api.html#reactchildrencount
*
* @param {?*} children Children tree container.
* @return {number} The number of children.
*/
function countChildren(children) {return traverseAllChildren(children, () => null, null);
}
/**
* Flatten a children object (typically specified as `props.children`) and
* return an array with appropriately re-keyed children.
* 展开一个成一个数组
* See https://reactjs.org/docs/react-api.html#reactchildrentoarray
*/
function toArray(children) {const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, child => child);
return result;
}
/**
* Returns the first child in a collection of children and verifies that there
* is only one child in the collection.
* 返回子集合中的 first child 并验证 只有一个
*
* See https://reactjs.org/docs/react-api.html#reactchildrenonly
*
* The current implementation of this function assumes that a single child gets
* passed without a wrapper, but the purpose of this helper function is to
* abstract away the particular structure of children.
*
* @param {?object} children Child collection structure.
* @return {ReactElement} The first and only `ReactElement` contained in the
* structure.
*/
function onlyChild(children) {
invariant(isValidElement(children),
'React.Children.only expected to receive a single React element child.',
);
return children;
}
export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};
正文完
发表至: javascript
2019-11-10