Spec-Zone.ru › React 18

Дети

Проблема

Использование Children нестандартно и может привести к хрупкому коду. См. общие альтернативы.

Children позволяет манипулировать и преобразовывать JSX, полученный как свойство children.

const mappedChildren = Children.map(children, child =>
  <div className="Row">
    {child}
  </div>
);
  • Справочник
    • Children.count(children)
    • Children.forEach(children, fn, thisArg?)
    • Children.map(children, fn, thisArg?)
    • Children.only(children)
    • Children.toArray(children)
  • Использование
    • Преобразование детей
    • Выполнение кода для каждого ребенка
    • Подсчёт детей
    • Преобразование детей в массив
  • Альтернативы
    • Экспонирование нескольких компонентов
    • Приём массива объектов как свойства
    • Вызов render-свойства для настройки рендеринга
  • Отладка
    • Я передаю пользовательский компонент, но методы Children не отображают результат его рендеринга

Справочник

Children.count(children)

Вызовите Children.count(children) для подсчета количества детей в структуре данных children.

import { Children } from 'react';

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

См. дополнительные примеры ниже.

Параметры

  • children: Значение свойства children, полученное вашим компонентом.

Возвращает

Количество узлов внутри этих children.

Ограничения

  • Пустые узлы (null, undefined, и булевы значения), строки, числа и React-элементы считаются отдельными узлами. Массивы не считаются отдельными узлами, но их дети считаются. Обход не уходит глубже, чем React-элементы: они не рендерятся, и их дети не обходятся. Фрагменты не обходятся.

Children.forEach(children, fn, thisArg?)

Вызовите Children.forEach(children, fn, thisArg?) для выполнения кода для каждого ребёнка в структуре данных children.

import { Children } from 'react';

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

См. дополнительные примеры ниже.

Параметры

  • children: Значение свойства children, полученное вашим компонентом.
  • fn: Функция, которую нужно выполнить для каждого ребёнка, аналогично методу forEach массива. Она вызывается с ребёнком в качестве первого аргумента и его индексом во втором. Индекс начинается с 0 и увеличивается при каждом вызове.
  • необязательно thisArg: Значение this для вызова функции fn. Если опущено, используется значение undefined.

Возвращает

Children.forEach возвращает undefined.

Ограничения

  • Пустые узлы (null, undefined, и булевы значения), строки, числа и React-элементы считаются отдельными узлами. Массивы не считаются отдельными узлами, но их дети считаются. Обход не уходит глубже, чем React-элементы: они не рендерятся, и их дети не обходятся. Фрагменты не обходятся.

Children.map(children, fn, thisArg?)

Вызовите Children.map(children, fn, thisArg?) для отображения или преобразования каждого ребёнка в структуре данных children.

import { Children } from 'react';

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

См. дополнительные примеры ниже.

Параметры

  • children: Значение свойства children, полученное вашим компонентом.
  • fn: Функция отображения, аналогичная методу map массива. Она вызывается с ребёнком в качестве первого аргумента и его индексом во втором. Индекс начинается с 0 и увеличивается при каждом вызове. Вам нужно вернуть React-узел из этой функции. Это может быть пустой узел (null, undefined, или булево значение), строка, число, React-элемент или массив других React-узлов.
  • необязательно thisArg: Значение this для вызова функции fn. Если опущено, используется значение undefined.

Возвращает

Если children является null или undefined, возвращается то же значение.

В противном случае возвращается плоский массив, состоящий из узлов, которые вы вернули из функции fn. Возвращаемый массив будет содержать все узлы, которые вы вернули, за исключением null и undefined.

Ограничения

  • Пустые узлы (null, undefined, и булевы значения), строки, числа и React-элементы считаются отдельными узлами. Массивы не считаются отдельными узлами, но их дети считаются. Обход не уходит глубже, чем React-элементы: они не рендерятся, и их дети не обходятся. Фрагменты не обходятся.

  • Если вы возвращаете элемент или массив элементов с ключами из fn, ключи возвращаемых элементов будут автоматически объединены с ключом соответствующего исходного элемента из children. Когда вы возвращаете несколько элементов из fn в массиве, их ключи должны быть уникальными только локально друг для друга.


Children.only(children)

Вызовите Children.only(children) для проверки того, что children представляет собой единственный React-элемент.

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

Параметры

  • children: Значение свойства children, полученное вашим компонентом.

Возвращает

Если children является допустимым элементом, возвращает этот элемент.

В противном случае выбрасывает ошибку.

Ограничения

  • Этот метод всегда выбрасывает ошибку, если вы передаете массив (например, возвращаемое значение Children.map) в качестве children. Другими словами, он проверяет, что children является единственным React-элементом, а не массивом с одним элементом.

Children.toArray(children)

Вызовите Children.toArray(children) для создания массива из структуры данных children.

import { Children } from 'react';

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

Параметры

  • children: Значение свойства children, полученное вашим компонентом.

Возвращает

Возвращает плоский массив элементов в children.

Ограничения

  • Пустые узлы (null, undefined, и булевы значения) будут пропущены в возвращаемом массиве. Ключи возвращаемых элементов вычисляются из ключей исходных элементов и их уровня вложенности и позиции. Это гарантирует, что выравнивание массива не вносит изменений в поведение.

Использование

Преобразование детей

Чтобы преобразовать JSX детей, который ваш компонент получает как свойство children, вызовите 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:

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

Тогда, с реализацией RowList выше, конечный рендер будет выглядеть так:

<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 не всегда является массивом?

В React свойство children рассматривается как непрозрачная структура данных. Это означает, что вы не должны полагаться на её структуру. Для преобразования, фильтрации или подсчёта потомков следует использовать методы Children.

На практике, структура данных children часто представляется как массив внутри. Однако, если есть только один потомок, React не создаёт дополнительный массив, так как это приведёт к ненужным затратам памяти. Пока вы используете методы Children вместо прямого анализа свойства children, ваш код не сломается даже если React изменит реализацию структуры данных.

Даже когда children является массивом, Children.map имеет полезные особенности. Например, Children.map комбинирует ключи на возвращаемых элементах с ключами на элементах children, которые вы передали. Это гарантирует, что исходные JSX-потомки не «теряют» ключи, даже если они оборачиваются, как в примере выше.

Опасная ловушка

Структура данных children не включает отрендеренный вывод компонентов, которые вы передаёте в качестве JSX. В примере ниже, children полученное компонентом RowList содержит только два элемента, а не три:

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

Вот почему в этом примере генерируется только две обертки строк:

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>
    </>
  );
}

Нет способа получить отрендеренный вывод внутреннего компонента, такого как <MoreRows />, при манипулировании children. Поэтому лучше использовать одно из альтернативных решений.


Выполнение некоторого кода для каждого потомка

Вызовите Children.forEach для итерации по каждому потомку в структуре данных children. Он не возвращает никакого значения и похож на метод forEach массива. Вы можете использовать его для выполнения пользовательской логики, например, для построения собственного массива.

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. Поэтому лучше использовать одно из альтернативных решений.


Подсчёт потомков

Вызовите Children.count(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. Поэтому лучше использовать одно из альтернативных решений.


Преобразование потомков в массив

Вызовите Children.toArray(children) для преобразования структуры данных children в обычный JavaScript-массив. Это позволяет манипулировать массивом с помощью встроенных методов массивов, таких как filter, sort или reverse.

import { Children } from 'react';

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

Опасная ловушка

Как упоминалось ранее, нет способа получить отрендеренный вывод внутреннего компонента при манипулировании children. Поэтому лучше использовать одно из альтернативных решений.


Альтернативы

Примечание

Этот раздел описывает альтернативы API Children (с заглавными C ), который импортируется так:

import { Children } from 'react';

Не путайте его с использованием свойства children (строчные c ), что хорошо и поощряется.

Экспонирование нескольких компонентов

Манипулирование потомками с помощью методов Children часто приводит к хрупкому коду. Когда вы передаёте потомков компоненту в JSX, вы обычно не ожидаете, что компонент будет манипулировать или преобразовывать отдельных потомков.

По возможности, старайтесь избегать использования методов Children. Например, если вы хотите, чтобы каждый потомок RowList был обернут в <div className="Row">, экспортируйте компонент Row и вручную оберните каждую строку в него, как в этом примере:

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:

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 /> как единственный потомок (и единственную строку).


Приём массива объектов в качестве свойства

Вы также можете явно передать массив в качестве свойства. Например, этот RowList принимает массив rows в качестве свойства:

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.

Этот шаблон особенно полезен, когда вы хотите передать больше информации в виде структурированных данных вместе с потомками. В примере ниже, компонент TabSwitcher получает массив объектов в качестве свойства tabs:

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.


Вызов render-свойства для настройки рендеринга

Вместо того, чтобы генерировать JSX для каждого элемента, вы также можете передать функцию, которая возвращает JSX, и вызвать эту функцию при необходимости. В этом примере компонент App передает функцию renderContent компоненту TabSwitcher. Компонент TabSwitcher вызывает renderContent только для выбранной вкладки:

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, называется render-свойством, потому что это свойство, которое определяет, как отобразить часть пользовательского интерфейса. Однако в нём нет ничего особенного: это обычное свойство, которое случайно является функцией.

Render-свойства являются функциями, поэтому вы можете передавать им информацию. Например, этот компонент RowList передает id и index каждой строки в renderRow render-свойство, которое использует index для выделения чётных строк:

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> 
        );
      }}
    />
  );
}

Это ещё один пример того, как родительские и дочерние компоненты могут взаимодействовать без манипулирования потомками.


Отладка

Я передаю пользовательский компонент, но методы children не показывают его результат рендеринга

Предположим, вы передаёте два потомка компоненту RowList так:

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

Если вы делаете Children.count(children) внутри RowList, вы получите 2. Даже если MoreRows отрендерит 10 различных элементов или вернёт null, Children.count(children) всё равно будет 2. С точки зрения RowList , он видит только JSX, который он получил. Он не видит внутренности компонента MoreRows.

Ограничение затрудняет извлечение компонента. Вот почему предпочтительнее использовать альтернативы вместо использования Children.

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/reference/react/Children

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API