易犯错误

使用 Children 不常见,并且可能导致代码脆弱。查看常见替代方案。

🌐 Using Children is uncommon and can lead to fragile code. See common alternatives.

Children 允许你操作和转换你作为 children 属性 收到的 JSX。

const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);

参考

🌐 Reference

Children.count(children)

调用 Children.count(children) 来计算 children 数据结构中子元素的数量。

🌐 Call Children.count(children) to count the number of children in the children data structure.

import { Children } from 'react';

function RowList({ children }) {
return (
<>
<h1>Total rows: {Children.count(children)}</h1>
...
</>
);
}

查看更多示例。

参数

🌐 Parameters

返回

🌐 Returns

这些 children 内的节点数量。

🌐 The number of nodes inside these children.

注意事项

🌐 Caveats

  • 空节点(nullundefined 和布尔值)、字符串、数字以及 React 元素 都算作单独的节点。数组本身不算作单独的节点,但它们的子节点算作。遍历不会深入 React 元素内部: 它们不会被渲染,它们的子节点也不会被遍历。Fragments 不会被遍历。

Children.forEach(children, fn, thisArg?)

调用 Children.forEach(children, fn, thisArg?) 来为 children 数据结构中的每个子项运行一些代码。

🌐 Call Children.forEach(children, fn, thisArg?) to run some code for each child in the children data structure.

import { Children } from 'react';

function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...

查看更多示例。

参数

🌐 Parameters

  • children:你的组件接收到的 children 属性 的值。
  • fn:你想要为每个子项运行的函数,类似于 数组 forEach 方法 的回调。它将以子项作为第一个参数,以其索引作为第二个参数调用。索引从 0 开始,并在每次调用时递增。
  • 可选 thisArg:应使用 this 调用 fn 函数。如果省略,则为 undefined

返回

🌐 Returns

Children.forEach 返回 undefined

注意事项

🌐 Caveats

  • 空节点(nullundefined 和布尔值)、字符串、数字以及 React 元素 都算作单独的节点。数组本身不算作单独的节点,但它们的子节点算作。遍历不会深入 React 元素内部: 它们不会被渲染,它们的子节点也不会被遍历。Fragments 不会被遍历。

Children.map(children, fn, thisArg?)

调用 Children.map(children, fn, thisArg?) 来映射或转换 children 数据结构中的每个子项。

🌐 Call Children.map(children, fn, thisArg?) to map or transform each child in the children data structure.

import { Children } from 'react';

function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}

查看更多示例。

参数

🌐 Parameters

  • children:你的组件接收到的 children 属性 的值。
  • fn:映射函数,类似于 数组 map 方法 的回调。它会以子元素作为第一个参数,以其索引作为第二个参数调用。索引从 0 开始,每次调用递增。你需要从此函数返回一个 React 节点。这个节点可以是空节点(nullundefined 或布尔值)、字符串、数字、React 元素,或者是其他 React 节点的数组。
  • 可选 thisArg:应使用 this 调用 fn 函数。如果省略,则为 undefined

返回

🌐 Returns

如果 childrennullundefined,则返回相同的值。

🌐 If children is null or undefined, returns the same value.

否则,将返回一个由你从 fn 函数返回的节点组成的扁平数组。返回的数组将包含你返回的所有节点,但不包括 nullundefined

🌐 Otherwise, returns a flat array consisting of the nodes you’ve returned from the fn function. The returned array will contain all nodes you returned except for null and undefined.

注意事项

🌐 Caveats

  • 空节点(nullundefined 和布尔值)、字符串、数字以及 React 元素 都算作单独的节点。数组本身不算作单独的节点,但它们的子节点算作。遍历不会深入 React 元素内部: 它们不会被渲染,它们的子节点也不会被遍历。Fragments 不会被遍历。
  • 如果你返回一个元素或一个带有 fn 键的元素数组,返回的元素的键将会自动与 children 中对应原始项的键组合。 当你从 fn 返回多个元素组成的数组时,它们的键只需要在彼此之间局部唯一。

Children.only(children)

调用 Children.only(children) 以断言 children 表示单个 React 元素。

🌐 Call Children.only(children) to assert that children represent a single React element.

function Box({ children }) {
const element = Children.only(children);
// ...

参数

🌐 Parameters

返回

🌐 Returns

如果 children 是一个有效元素, 则返回该元素。

🌐 If children is a valid element, returns that element.

否则,抛出错误。

🌐 Otherwise, throws an error.

注意事项

🌐 Caveats

  • 如果你将数组(例如 Children.map 的返回值)作为 children 传递,这种方法总是会抛出异常。换句话说,它强制要求 children 是一个单一的 React 元素,而不是包含单个元素的数组。

Children.toArray(children)

调用 Children.toArray(children) 来从 children 数据结构创建一个数组。

🌐 Call Children.toArray(children) to create an array out of the children data structure.

import { Children } from 'react';

export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...

参数

🌐 Parameters

返回

🌐 Returns

返回 children 中的元素平铺数组。

🌐 Returns a flat array of elements in children.

注意事项

🌐 Caveats

  • 空节点(nullundefined 和布尔值)将在返回的数组中被省略。返回元素的键将根据原始元素的键及其嵌套层级和位置进行计算。 这确保了数组扁平化不会引入行为上的变化。

用法

🌐 Usage

子组件变形

🌐 Transforming children

要转换你的组件接收到的 children JSX(作为 children 属性),请调用 Children.map

🌐 To transform the children JSX that your component receives as the children prop, call Children.map:

import { Children } from 'react';

function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}

在上面的例子中,RowList 将它接收到的每个子元素封装在一个 <div className="Row"> 容器中。例如,假设父组件将三个 <p> 标签作为 children 属性传递给 RowList

🌐 In the example above, the RowList wraps every child it receives into a <div className="Row"> container. For example, let’s say the parent component passes three <p> tags as the children prop to RowList:

<RowList>
<p>This is the first item.</p>
<p>This is the second item.</p>
<p>This is the third item.</p>
</RowList>

然后,使用上面的 RowList 实现,最终渲染结果将如下所示:

🌐 Then, with the RowList implementation above, the final rendered result will look like this:

<div className="RowList">
<div className="Row">
<p>This is the first item.</p>
</div>
<div className="Row">
<p>This is the second item.</p>
</div>
<div className="Row">
<p>This is the third item.</p>
</div>
</div>

Children.map 类似于 使用 map() 转换数组。不同之处在于,children 数据结构被认为是不透明的。这意味着即使它有时是数组,你也不应该假设它是数组或任何其他特定的数据类型。这就是为什么如果你需要转换它,应该使用 Children.map

import { Children } from 'react';

export default function RowList({ children }) {
  return (
    <div className="RowList">
      {Children.map(children, child =>
        <div className="Row">
          {child}
        </div>
      )}
    </div>
  );
}

深入研究

为什么 children 属性不总是数组?

🌐 Why is the children prop not always an array?

在 React 中,children 属性被认为是一个不透明的数据结构。这意味着你不应该依赖它的结构。要对子元素进行转换、过滤或计数,你应该使用 Children 方法。

🌐 In React, the children prop is considered an opaque data structure. This means that you shouldn’t rely on how it is structured. To transform, filter, or count children, you should use the Children methods.

实际上,children 数据结构通常在内部表示为数组。然而,如果只有一个子元素,React 就不会创建额外的数组,因为这会导致不必要的内存开销。只要你使用 Children 方法而不是直接检查 children 属性,即使 React 改变了数据结构的实际实现,你的代码也不会出错。

🌐 In practice, the children data structure is often represented as an array internally. However, if there is only a single child, then React won’t create an extra array since this would lead to unnecessary memory overhead. As long as you use the Children methods instead of directly introspecting the children prop, your code will not break even if React changes how the data structure is actually implemented.

即使 children 是一个数组,Children.map 仍然具有有用的特殊行为。例如,Children.map 会将返回元素上的 keys 与你传递给它的 children 上的 keys 结合起来。这确保了原始的 JSX 子元素即使被像上面的例子那样封装,也不会“丢失” keys。

🌐 Even when children is an array, Children.map has useful special behavior. For example, Children.map combines the keys on the returned elements with the keys on the children you’ve passed to it. This ensures the original JSX children don’t “lose” keys even if they get wrapped like in the example above.

易犯错误

children 数据结构不包含你作为 JSX 传入的组件的渲染输出。在下面的示例中,RowList 接收到的 children 仅包含两个项目,而不是三个:

🌐 The children data structure does not include rendered output of the components you pass as JSX. In the example below, the children received by the RowList only contains two items rather than three:

  1. <p>This is the first item.</p>
  2. <MoreRows />

这就是为什么本例中只生成了两个行封装器:

🌐 This is why only two row wrappers are generated in this example:

import RowList from './RowList.js';

export default function App() {
  return (
    <RowList>
      <p>This is the first item.</p>
      <MoreRows />
    </RowList>
  );
}

function MoreRows() {
  return (
    <>
      <p>This is the second item.</p>
      <p>This is the third item.</p>
    </>
  );
}

在操作 children 时,无法获得内部组件(如 <MoreRows />)的渲染输出。这就是为什么通常使用其他解决方案更好的原因。


为每个子组件运行部分代码

🌐 Running some code for each child

调用 Children.forEach 来遍历 children 数据结构中的每个子元素。它不返回任何值,类似于 数组 forEach 方法。你可以使用它来运行自定义逻辑,例如构建你自己的数组。

🌐 Call Children.forEach to iterate over each child in the children data structure. It does not return any value and is similar to the array forEach method. You can use it to run custom logic like constructing your own array.

import { Children } from 'react';

export default function SeparatorList({ children }) {
  const result = [];
  Children.forEach(children, (child, index) => {
    result.push(child);
    result.push(<hr key={index} />);
  });
  result.pop(); // Remove the last separator
  return result;
}

易犯错误

如前所述,在操作 children 时无法获取内部组件的渲染输出。这就是为什么通常更好使用替代解决方案之一。

🌐 As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating children. This is why it’s usually better to use one of the alternative solutions.


计数子元素

🌐 Counting children

调用 Children.count(children) 来计算子级的数量。

🌐 Call Children.count(children) to calculate the number of children.

import { Children } from 'react';

export default function RowList({ children }) {
  return (
    <div className="RowList">
      <h1 className="RowListHeader">
        Total rows: {Children.count(children)}
      </h1>
      {Children.map(children, child =>
        <div className="Row">
          {child}
        </div>
      )}
    </div>
  );
}

易犯错误

如前所述,在操作 children 时无法获取内部组件的渲染输出。这就是为什么通常更好使用替代解决方案之一。

🌐 As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating children. This is why it’s usually better to use one of the alternative solutions.


将子元素转换为数组

🌐 Converting children to an array

调用 Children.toArray(children)children 数据结构转换为普通的 JavaScript 数组。这让你可以使用内置的数组方法,如 filtersortreverse 来操作数组。

🌐 Call Children.toArray(children) to turn the children data structure into a regular JavaScript array. This lets you manipulate the array with built-in array methods like filter, sort, or reverse.

import { Children } from 'react';

export default function ReversedList({ children }) {
  const result = Children.toArray(children);
  result.reverse();
  return result;
}

易犯错误

如前所述,在操作 children 时无法获取内部组件的渲染输出。这就是为什么通常更好使用替代解决方案之一。

🌐 As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating children. This is why it’s usually better to use one of the alternative solutions.


备选方案

🌐 Alternatives

注意

本节描述了 Children API 的替代方案(大写的 C),导入方法如下:

🌐 This section describes alternatives to the Children API (with capital C) that’s imported like this:

import { Children } from 'react';

不要将其与使用 children 属性(小写的 c)混淆,这是好的并且推荐的。

🌐 Don’t confuse it with using the children prop (lowercase c), which is good and encouraged.

公开多个组件

🌐 Exposing multiple components

使用 Children 方法操作子元素通常会导致脆弱的代码。当你在 JSX 中将子元素传递给一个组件时,你通常不期望组件会操作或转换各个子元素。

🌐 Manipulating children with the Children methods often leads to fragile code. When you pass children to a component in JSX, you don’t usually expect the component to manipulate or transform the individual children.

在可以的情况下,尽量避免使用 Children 方法。例如,如果你想让 RowList 的每个子元素都被封装在 <div className="Row"> 中,导出一个 Row 组件,并像这样手动将每一行封装起来:

🌐 When you can, try to avoid using the Children methods. For example, if you want every child of RowList to be wrapped in <div className="Row">, export a Row component, and manually wrap every row into it like this:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList>
      <Row>
        <p>This is the first item.</p>
      </Row>
      <Row>
        <p>This is the second item.</p>
      </Row>
      <Row>
        <p>This is the third item.</p>
      </Row>
    </RowList>
  );
}

与使用 Children.map 不同,这种方法不会自动封装每个子元素。**然而,与之前使用 Children.map 的示例相比,这种方法有一个显著的优点,因为即使你继续提取更多组件,它仍然有效。**例如,如果你提取你自己的 MoreRows 组件,它仍然可以工作:

🌐 Unlike using Children.map, this approach does not wrap every child automatically. However, this approach has a significant benefit compared to the earlier example with Children.map because it works even if you keep extracting more components. For example, it still works if you extract your own MoreRows component:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList>
      <Row>
        <p>This is the first item.</p>
      </Row>
      <MoreRows />
    </RowList>
  );
}

function MoreRows() {
  return (
    <>
      <Row>
        <p>This is the second item.</p>
      </Row>
      <Row>
        <p>This is the third item.</p>
      </Row>
    </>
  );
}

这在 Children.map 上行不通,因为它会将 <MoreRows /> 视为一个子元素(也是一行)。

🌐 This wouldn’t work with Children.map because it would “see” <MoreRows /> as a single child (and a single row).


接受对象数组作为 prop

🌐 Accepting an array of objects as a prop

你也可以显式地将数组作为属性传递。例如,这个 RowList 接受一个 rows 数组作为属性:

🌐 You can also explicitly pass an array as a prop. For example, this RowList accepts a rows array as a prop:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList rows={[
      { id: 'first', content: <p>This is the first item.</p> },
      { id: 'second', content: <p>This is the second item.</p> },
      { id: 'third', content: <p>This is the third item.</p> }
    ]} />
  );
}

由于 rows 是一个常规的 JavaScript 数组,RowList 组件可以在它上面使用内置的数组方法,例如 map

🌐 Since rows is a regular JavaScript array, the RowList component can use built-in array methods like map on it.

当你希望能够将更多信息作为结构化数据与子组件一起传递时,这种模式尤其有用。在下面的示例中,TabSwitcher 组件将一个对象数组作为 tabs 属性接收:

🌐 This pattern is especially useful when you want to be able to pass more information as structured data together with children. In the below example, the TabSwitcher component receives an array of objects as the tabs prop:

import TabSwitcher from './TabSwitcher.js';

export default function App() {
  return (
    <TabSwitcher tabs={[
      {
        id: 'first',
        header: 'First',
        content: <p>This is the first item.</p>
      },
      {
        id: 'second',
        header: 'Second',
        content: <p>This is the second item.</p>
      },
      {
        id: 'third',
        header: 'Third',
        content: <p>This is the third item.</p>
      }
    ]} />
  );
}

与将子元素作为 JSX 传递不同,这种方法允许你将一些额外的数据(如 header)与每个项目关联。因为你直接操作的是 tabs,并且它是一个数组,所以你不需要使用 Children 方法。

🌐 Unlike passing the children as JSX, this approach lets you associate some extra data like header with each item. Because you are working with the tabs directly, and it is an array, you do not need the Children methods.


调用渲染属性自定义渲染

🌐 Calling a render prop to customize rendering

你可以不为每个单独的项目生成 JSX,也可以传递一个返回 JSX 的函数,并在必要时调用该函数。在这个例子中,App 组件将一个 renderContent 函数传递给 TabSwitcher 组件。TabSwitcher 组件仅为选中的标签调用 renderContent

🌐 Instead of producing JSX for every single item, you can also pass a function that returns JSX, and call that function when necessary. In this example, the App component passes a renderContent function to the TabSwitcher component. The TabSwitcher component calls renderContent only for the selected tab:

import TabSwitcher from './TabSwitcher.js';

export default function App() {
  return (
    <TabSwitcher
      tabIds={['first', 'second', 'third']}
      getHeader={tabId => {
        return tabId[0].toUpperCase() + tabId.slice(1);
      }}
      renderContent={tabId => {
        return <p>This is the {tabId} item.</p>;
      }}
    />
  );
}

renderContent 这样的 prop 被称为 render prop,因为它是一个指定如何渲染用户界面部分的 prop。然而,它没有什么特别的:它只是一个恰好是函数的普通 prop。

🌐 A prop like renderContent is called a render prop because it is a prop that specifies how to render a piece of the user interface. However, there is nothing special about it: it is a regular prop which happens to be a function.

Render props 是函数,所以你可以向它们传递信息。例如,这个 RowList 组件将每行的 idindex 传递给 renderRow render prop,而 renderRow 使用 index 来高亮偶数行:

🌐 Render props are functions, so you can pass information to them. For example, this RowList component passes the id and the index of each row to the renderRow render prop, which uses index to highlight even rows:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList
      rowIds={['first', 'second', 'third']}
      renderRow={(id, index) => {
        return (
          <Row isHighlighted={index % 2 === 0}>
            <p>This is the {id} item.</p>
          </Row> 
        );
      }}
    />
  );
}

这是另一个父子组件如何在不操作子组件的情况下进行协作的示例。

🌐 This is another example of how parent and child components can cooperate without manipulating the children.


故障排除

🌐 Troubleshooting

我传递了一个自定义组件,但 Children 方法没有显示它的渲染结果

🌐 I pass a custom component, but the Children methods don’t show its render result

假设你像这样将两个子节点传递给 RowList

🌐 Suppose you pass two children to RowList like this:

<RowList>
<p>First item</p>
<MoreRows />
</RowList>

如果你在 RowList 内执行 Children.count(children),你将得到 2。即使 MoreRows 渲染了 10 个不同的项目,或者返回了 nullChildren.count(children) 仍然会是 2。从 RowList 的角度来看,它只“看到”它收到的 JSX。它不会“看到” MoreRows 组件的内部。

🌐 If you do Children.count(children) inside RowList, you will get 2. Even if MoreRows renders 10 different items, or if it returns null, Children.count(children) will still be 2. From the RowList’s perspective, it only “sees” the JSX it has received. It does not “see” the internals of the MoreRows component.

这个限制使得提取组件变得困难。这就是为什么相比使用 Children,更倾向于使用替代方案的原因。

🌐 The limitation makes it hard to extract a component. This is why alternatives are preferred to using Children.