Children
参考
Children.count(children)
调用 Children.count(children)
可以获取 children
中的节点数量。
。
参数
children
:组件接收到的 。
返回值
children
中的节点数量。
注意事项
- 空节点(
null
,undefined
以及布尔值),字符串,数字和 都会被统计为一个节点。在遍历统计的过程中,React 元素不会被渲染,所以其子节点不会被统计。 也不会被统计。对于数组,它本身也不会被统计,但其中的元素遵循上述规则。
Children.forEach(children, fn, thisArg?)
调用 Children.forEach(children, fn, thisArg?)
可以为每个 children
中的每个子节点执行一段代码。
。
参数
children
:组件接收到的 。fn
:和 中的回调类似,是你希望为每个子节点执行的函数。当这个函数执行时,对应的子节点和其下标将分别作为函数的第一、第二个参数,下标从0
开始自增。- 可选
thisArg
:为fn
函数绑定 。默认值为undefined
。
返回值
Children.forEach
返回值是 undefined
。
注意事项
- 空节点(
null
,undefined
以及布尔值),字符串,数字和 都会被统计为单个节点。在遍历统计的过程中,React 元素不会被渲染,所以其子节点不会被统计。 也不会被统计。对于数组,它本身也不会被统计,但其中的元素遵循上述规则。
Children.map(children, fn, thisArg?)
调用 Children.map(children, fn, thisArg?)
可以对 children
中的每个子节点进行映射或转换。
。
参数
children
:组件接收到的 。fn
:和 中的回调类似,是一个映射函数。当这个函数执行时,对应的子节点和其下标将分别作为函数的第一、第二个参数,下标从0
开始自增。你需要使这个映射函数返回一个 React 节点,它可以是一个空节点(null
,undefined
)。- 可选
thisArg
:为fn
函数绑定 。默认值为undefined
。
返回值
如果 children
是 null
或者 undefined
,那么就返回这个值。
否则就返回一个由 fn
函数返回节点组成的一维数组。这个数组将包含除 null
和 undefined
以外的所有节点。
注意事项
空节点(
null
,undefined
以及布尔值),字符串,数字和 都会被统计为单个节点。在遍历统计的过程中,React 元素不会被渲染,所以其子节点不会被统计。 也不会被统计。对于数组,它本身也不会被统计,但其中的元素遵循上述规则。如果你在
fn
中返回了一个具有 key 的元素或者元素数组,各个元素的 key 将自动与其在children
中对应的原始项的 key 绑定。当你在fn
中返回了一个包含了多个元素的数组时,其中的每个元素的 key 都需要保证在这个数组中是独一无二的。
Children.only(children)
调用 Children.only(children)
能够断言 children
代表一个 React 元素。
参数
children
:组件接收到的 。
返回值
如果 children
,那么就会返回这个元素。
否则会抛出一个异常。
注意事项
- 如果传入一个数组(比如
Children.map
的返回值)作为children
,那么这个方法会抛出异常。也就是说,这个方法强制要求children
是一个 React 元素,而不是一个元素数组。
Children.toArray(children)
调用 Children.toArray(children)
能够通过 children
创建一个数组。
参数
children
:组件接收到的 。
返回值
返回一个由 children
中的元素构成的一维数组。
注意事项
- 空节点(
null
,undefined
以及 布尔值)将在返回的数组中被忽略掉。返回的元素的 key 将根据原始元素的 key 和其嵌套层级与位置进行计算得到。这保证了扁平化数组时不会更改原本的行为。
用法
转化 children
如果想修改组件 ,那么可以使用 Children.map
:
在上述例子中,RowList
用 <div className="Row">
包裹了接收到的每一个子元素。举个例子,假设父组件将三个 <p>
作为 children
属性传递给 RowList
:
然后,使用实现上面的 RowList
,最终的渲染结果将是像下面这样:
Children.map
和 。区别在于 children
被视为 不透明的。这意味着即使有时它真的是一个数组,你也不应该假设它是一个数组或者其他数据类型。这就是为什么如果你要转换children
, 应该使用 Children.map
。
深入探讨
为什么 children 属性并不总是一个数组?
为什么 children 属性并不总是一个数组?
在 React 中,children
属性是被视为 不透明的 数据结构。这意味着你不应该依赖它的结构。如果要转换,过滤,或者统计子节点,你应该使用 Children
方法。
实际操作过程中,children
在底层常常被表示为数组。但是如果这里只有一个子节点,那么 React 将不会创建数组,因为这将导致不必要的内存开销。只要你使用 Children
方法而不是直接操作 children
底层结构,即使 React 改变了 children
数据结构的实际实现方式,你的代码也不会被中断。
当 children
是一个数组时,Children.map
会有许多有用的特性。比如,Children.map
将被返回元素上的 和 你传递给它的 children
上的 key 绑定。这保证了原本的 JSX 子元素不会“丢失” key,即使它们上面的例子中那样被包裹。
为每一个子元素执行一段代码
调用 Children.forEach
能够迭代 children
数据结构中的每一个子节点。它并不会返回任何值,这和 类似。你可以使用它来运行自定义逻辑,例如构造自己的数组。
统计子节点
调用 Children.count(children)
能够计算子节点的数量。
将 children 转化为数组
通过调用 Children.toArray(children)
将 children
变为一个常规的 JavaScript 数组。这使得你能够使用 , , 或者 等数组内置方法来操作这个数组。
替代方案
暴露多个组件
使用 Children
方法操作子节点通常会削弱代码的健壮性。在 JSX 中将子节点传递给组件时,通常不希望操作或转换子节点。
如果能够的话,尽量避免使用 Children
方法。例如,如果你希望 RowList
的每一个子节点都被 <div className="Row">
包裹,那么可以导出一个 Row
组件,然后像下面这样手动把包裹每一行:
和使用 Children.map
不同,这种方式不会自动包裹每个子节点。但是,和 相比,这种方式具有明显的优势,因为即使你继续抽离更多的组件,它也仍然有效。
这里使用 Children.map
得不到一样的结果,因为它会“认为” <MoreRows>
只是一个单独的子节点(并且只占据了一行)。
接收对象数组作为参数
你也可以显示地传递一个数组作为组件的参数。例如,下面的 RowList
接收了一个 rows
数组作为组件的参数:
因为 rows
是一个常规的 JavaScript 数组,RowList
组件可以对其使用 等数组内置方法。
当你希望能够将更多信息作为结构化数据,与子节点一起传递时,这个方案将会非常有用。在下面的示例中,TabSwitcher
接收了一个对象数组作为 tabs
的属性:
和将子节点作为 JSX 传递不同,这个方法允许你将一些额外的数据,比如 header
,与每个子项关联。因为你直接使用 tabs
,并且它是一个数组,所以你并不需要 Children
方法。
调用渲染属性以自定义渲染
除了为每一个子项生成 JSX,你还可以传递一个返回值类型是 JSX 的函数,并且在必要的时候调用这个函数。在这个示例中,App
组件向 TabSwitcher
组件传递了一个 renderContent
函数。TabSwitcher
组件仅对被选中的 tab 调用 renderContent
。
像 renderContent
这样的参数会被称为渲染属性,因为它指定了如何渲染一部分用户交互界面。但是,它也并没有什么特别之处,只是一个普通的属性同时恰好又是一个函数。
渲染属性是函数,所以你可以向它们传递参数。比如,这里的 RowList
组件向 renderRow
传递了一个 id
和每一行的 index
,该属性用 index
来选择偶数行:
这是如何在不操纵子组件的情况下,父组件和子组件进行协作的另一个示例。
错误排查
我传递入了一个自定义组件,但是 Children
方法没有显示渲染的内容
假设你向 RowList
传入了两个子节点,像下面这样:
如果你在 RowList
中执行 Children.count(children)
,其返回值将为 2
。即使 MoreRows
渲染了 10 个不同的子项,或者返回了 null
,Children.count(children)
的返回值仍然是 2
。从 RowList
的角度上看,它只能感知到它直接接收到的 JSX,并不能感知到 MoreRows
组件的内部。
这导致抽离一个组件变得较为困难,这也是为什么我们更推荐使用 而不是使用 Children
。