React 子元素
一、作用
二、映射子元素
/**
* Maps children that are typically specified as `props.children`.
* 映射通常指定为 `props.children` 的子元素。
*
* See https://reactjs.org/docs/react-api.html#reactchildrenmap
*
* The provided mapFunction(child, index) will be called for each
* leaf child.
* 提供的 mapFunction(child, index) 将会被调用于每个叶子子节点。
*
* @param {?*} children Children tree container.
* @param {?*} children 子树容器
* @param {function(*, int)} func The map function.
* @param {function(*, int)} func map 实现函数
* @param {*} context Context for mapFunction.
* @param {*} context mapFunction 的上下文。
* @return {object} Object containing the ordered map of results.
* @return {object} 包含结果有序映射的对象。
*/
function mapChildren(
children: ?ReactNodeList,
func: MapFunc,
context: mixed,
): ?Array<React$Node> {
if (children == null) {
return children;
}
const result: Array<React$Node> = [];
let count = 0;
mapIntoArray(children, result, '', '', function (child) {
return func.call(context, child, count++);
});
return result;
}
三、统计子元素数量
/**
* Count the number of children that are typically specified as
* `props.children`.
* 计算通常作为 `props.children` 指定的子元素数量。
*
* See https://reactjs.org/docs/react-api.html#reactchildrencount
*
* @param {?*} children Children tree container.
* @param {?*} children 子元素树的容器
* @return {number} The number of children.
* @return {number} 子元素的数量
*/
function countChildren(children: ?ReactNodeList): number {
let n = 0;
mapChildren(children, () => {
n++;
// Don't return anything
// 不返回任何内容
});
return n;
}
四、遍历子元素
/**
* Iterates through children that are typically specified as `props.children`.
* 遍历通常指定为 `props.children` 的子元素。
*
* See https://reactjs.org/docs/react-api.html#reactchildrenforeach
*
* The provided forEachFunc(child, index) will be called for each
* leaf child.
* 提供的 forEachFunc(child, index) 将会对每个叶子子节点调用。
*
* @param {?*} children Children tree container.
* @param {?*} children 子元素树容器
* @param {function(*, int)} forEachFunc
* @param {*} forEachContext Context for forEachContext.
* @param {*} forEachContext 遍历子元素上下文的上下文
*/
function forEachChildren(
children: ?ReactNodeList,
forEachFunc: ForEachFunc,
forEachContext: mixed,
): void {
mapChildren(
children,
function () {
forEachFunc.apply(this, arguments);
// Don't return anything.
// 不返回任何内容
},
forEachContext,
);
}
五、转为为数组
/**
* Flatten a children object (typically specified as `props.children`) and
* return an array with appropriately re-keyed children.
*
* 将子对象(通常指定为 `props.children`)展开并返回一个具有适当重新键值的子元素数组。
*
* See https://reactjs.org/docs/react-api.html#reactchildrentoarray
*/
function toArray(children: ?ReactNodeList): Array<React$Node> {
return mapChildren(children, child => child) || [];
}
六、只是子元素
/**
* Returns the first child in a collection of children and verifies that there
* is only one child in the collection.
* 返回子集合中的第一个子元素,并验证集合中只有一个子元素
*
* 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.
* @param {?object} children 子集合结构
* @return {ReactElement} The first and only `ReactElement` contained in the
* structure.
* @return {ReactElement} 结构中包含的第一个也是唯一的 `ReactElement`
*/
function onlyChild<T>(children: T): T {
if (!isValidElement(children)) {
throw new Error(
'React.Children.only expected to receive a single React element child.',
);
}
return children;
}
七、常量
1. 分隔符
const SEPARATOR = '.';
2. 子分隔符
const SUBSEPARATOR = ':';
3. 用户提供的键转义正则
const userProvidedKeyEscapeRegex = /\/+/g;
八、变量
1. 已警告有关地图
/**
* TODO: Test that a single child and an array with one item have the same key
* pattern.
*
* 待办事项:测试单个子元素和包含一个项的数组是否具有相同的键模式。
*/
let didWarnAboutMaps = false;
九、工具
1. 转义键
/**
* Escape and wrap key so it is safe to use as a reactid
* 转义并包装键,以便安全地用作 reactid
*
* @param {string} key to be escaped.
* @param {string} key 要转义的键
* @return {string} the escaped key.
* @return {string} 转义键
*/
function escape(key: string): string {
const escapeRegex = /[=:]/g;
const escaperLookup = {
'=': '=0',
':': '=2',
};
const escapedString = key.replace(escapeRegex, function (match) {
return escaperLookup[match];
});
return '$' + escapedString;
}
2. 转义用户提供的密钥
function escapeUserProvidedKey(text: string): string {
return text.replace(userProvidedKeyEscapeRegex, '$&/');
}
3. 获取元素键
备注
checkKeyStringCoercion()由 CheckStringCoercion#checkKeyStringCoercion 实现
/**
* Generate a key string that identifies a element within a set.
* 生成一个用于标识集合中元素的键字符串。
*
* @param {*} element A element that could contain a manual key.
* @param {*} element 一个可能包含手动钥匙的元件。
* @param {number} index Index that is used if a manual key is not provided.
* @param {number} index 如果未提供手动键,则使用的索引。
* @return {string}
*/
function getElementKey(element: any, index: number): string {
// 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 element === 'object' && element !== null && element.key != null) {
if (enableOptimisticKey && element.key === REACT_OPTIMISTIC_KEY) {
// For React.Children purposes this is treated as just null.
// 对于 React.Children 而言,这仅被视为 null。
if (__DEV__) {
console.error("React.Children helpers don't support optimisticKey.");
}
return index.toString(36);
}
// Explicit key
// 显式键
if (__DEV__) {
checkKeyStringCoercion(element.key);
}
return escape('' + element.key);
}
// Implicit key determined by the index in the set
// 隐式键由集合中的索引决定
return index.toString(36);
}
4. 解决可处理对象
function resolveThenable<T>(thenable: Thenable<T>): T {
switch (thenable.status) {
case 'fulfilled': {
const fulfilledValue: T = thenable.value;
return fulfilledValue;
}
case 'rejected': {
const rejectedError = thenable.reason;
throw rejectedError;
}
default: {
if (typeof thenable.status === 'string') {
// Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation. We treat it as "pending".
// Attach a dummy listener, to ensure that any lazy initialization can
// happen. Flight lazily parses JSON when the value is actually awaited.
//
// 只有在状态未定义时才对 thenable 进行检测。如果状态已定义但值未知,则假设它已被某些
// 自定义用户空间实现检测过。我们将其视为“挂起”。
// 附加一个虚拟监听器,以确保可以进行任何延迟初始化。Flight 在实际等待值时才会延迟解析 JSON。
thenable.then(noop, noop);
} else {
// This is an uncached thenable that we haven't seen before.
// 这是一个我们以前未见过的未缓存可 then 对象。
// TODO: Detect infinite ping loops caused by uncached promises.
// 待办:检测由未缓存的 promise 引起的无限 ping 循环。
const pendingThenable: PendingThenable<T> = thenable as any;
pendingThenable.status = 'pending';
pendingThenable.then(
fulfilledValue => {
if (thenable.status === 'pending') {
const fulfilledThenable: FulfilledThenable<T> = thenable as any;
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = fulfilledValue;
}
},
(error: mixed) => {
if (thenable.status === 'pending') {
const rejectedThenable: RejectedThenable<T> = thenable as any;
rejectedThenable.status = 'rejected';
rejectedThenable.reason = error;
}
},
);
}
// Check one more time in case the thenable resolved synchronously.
// 再检查一次,以防 thenable 同步解决。
switch ((thenable as Thenable<T>).status) {
case 'fulfilled': {
const fulfilledThenable: FulfilledThenable<T> = thenable as any;
return fulfilledThenable.value;
}
case 'rejected': {
const rejectedThenable: RejectedThenable<T> = thenable as any;
const rejectedError = rejectedThenable.reason;
throw rejectedError;
}
}
}
}
throw thenable;
}
5. 映射到数组
备注
checkKeyStringCoercion()由 CheckStringCoercion#checkKeyStringCoercion 实现isValidElement()由 ReactJSXElement#isValidElement 实现getIteratorFn()由 ReactSymbols#getIteratorFn 实现
function mapIntoArray(
children: ?ReactNodeList,
array: Array<React$Node>,
escapedPrefix: string,
nameSoFar: string,
// callback: (?React$Node) => ?ReactNodeList,
callback: (reactNode?: React$Node) => ReactNodeList,
): number {
const type = typeof children;
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 'bigint':
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch ((children as any).$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
break;
case REACT_LAZY_TYPE:
const payload = (children as any)._payload;
const init = (children as any)._init;
return mapIntoArray(
init(payload),
array,
escapedPrefix,
nameSoFar,
callback,
);
}
}
}
if (invokeCallback) {
const child = children;
let mappedChild = callback(child);
// 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:
// 如果它是唯一的子元素,就把名字当作数组来处理这样当子元素数量增加时也能保持一致:
const childKey =
nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;
if (isArray(mappedChild)) {
let escapedChildKey = '';
if (childKey != null) {
escapedChildKey = escapeUserProvidedKey(childKey) + '/';
}
mapIntoArray(mappedChild, array, escapedChildKey, '', c => c);
} else if (mappedChild != null) {
if (isValidElement(mappedChild)) {
if (__DEV__) {
// The `if` statement here prevents auto-disabling of the safe
// coercion ESLint rule, so we must manually disable it below.
// 这里的 `if` 语句防止 ESLint 的安全类型转换规则被自动禁用,所以我们必须
// 在下面手动禁用它。
if (mappedChild.key != null) {
if (!child || child.key !== mappedChild.key) {
checkKeyStringCoercion(mappedChild.key);
}
}
}
const newChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
//
// 如果映射后的键和旧键不同,则同时保留它们,就像 traverseAllChildren 以前对
// 作为子元素的对象所做的那样
escapedPrefix +
(mappedChild.key != null &&
(!child || child.key !== mappedChild.key)
? escapeUserProvidedKey('' + mappedChild.key) + '/'
: '') +
childKey,
);
if (__DEV__) {
// If `child` was an element without a `key`, we need to validate if
// it should have had a `key`, before assigning one to `mappedChild`.
// 如果 `child` 是一个没有 `key` 的元素,我们需要在给 `mappedChild` 分配一个 `key` 之前,先验证
// 它是否应该有一个 `key`。
if (
nameSoFar !== '' &&
child != null &&
isValidElement(child) &&
child.key == null
) {
// We check truthiness of `child._store.validated` instead of being
// inequal to `1` to provide a bit of backward compatibility for any
// libraries (like `fbt`) which may be hacking this property.
//
// 我们检查 `child._store.validated` 的真实性,而不是判断是否不等于 `1`,
// 以便为任何可能修改该属性的库(例如 `fbt`)提供一些向后兼容性。
if (child._store && !child._store.validated) {
// Mark this child as having failed validation, but let the actual
// renderer print the warning later.
// 将此子项标记为验证失败,但让实际的渲染器稍后打印警告。
newChild._store.validated = 2;
}
}
}
mappedChild = newChild;
}
array.push(mappedChild);
}
return 1;
}
let child;
let nextName;
// 当前子树中找到的子节点数量。
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getElementKey(child, i);
subtreeCount += mapIntoArray(
child,
array,
escapedPrefix,
nextName,
callback,
);
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
const iterableChildren: Iterable<React$Node> & {
entries: any;
} = children as any;
if (__DEV__) {
// Warn about using Maps as children
// 警告:不要将 Maps 用作子元素
if (iteratorFn === iterableChildren.entries) {
if (!didWarnAboutMaps) {
console.warn(
'Using Maps as children is not supported. ' +
'Use an array of keyed ReactElements instead.',
);
}
didWarnAboutMaps = true;
}
}
const iterator = iteratorFn.call(iterableChildren);
let step;
let ii = 0;
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getElementKey(child, ii++);
subtreeCount += mapIntoArray(
child,
array,
escapedPrefix,
nextName,
callback,
);
}
} else if (type === 'object') {
if (typeof (children as any).then === 'function') {
return mapIntoArray(
resolveThenable(children as any),
array,
escapedPrefix,
nameSoFar,
callback,
);
}
const childrenString = String(children as any);
throw new Error(
`Objects are not valid as a React child (found: ${
childrenString === '[object Object]'
? 'object with keys {' +
Object.keys(children as any).join(', ') +
'}'
: childrenString
}). ` +
'If you meant to render a collection of children, use an array ' +
'instead.',
);
}
}
return subtreeCount;
}