React.Children.map рекурсивно?

Я создаю компонент React для отображения HTML-формы, и я нахожу необходимость рекурсивно перебирать все дочерние элементы моего родительского компонента Form, чтобы добавить дополнительные реквизиты только для дочерних компонентов определенного типа.

Пример (в JSX):

<Form>
    <p>Personal Information</p>
    <Input name="first_name" />
    <Input name="last_name" />
    <Input name="email" />
    <Label>
        Enter Your Birthday
        <Input name="birthday" type="date" />
    </Label>
</Form>

В этом примере я использую React.Children.map внутри моего компонента Form, а затем внутри функции map. Я проверяю дочерний тип "type" и "тип type.displayName" для определения того, с каким элементом я занимаюсь (либо собственный HTML-элемент, либо ReactElement):

var newChildren = React.Children.map(this.props.children, function(child) {
    if (is.inArray(child.type.displayName, supportedInputTypes)) {
      var extraChildProps = {
        alertColor: this.props.alertColor,
        displayErrors: this.state.displayErrors
      }
      return React.cloneElement(child, extraChildProps);
    } else {
      return child;
    }
  }.bind(this));

Моя проблема заключается в том, что React.Children.map неточно выполняет итерацию через this.props.children, и я бы хотел, чтобы он также проверял детей детей детей и т.д. Мне нужно добавить реквизиты только к моим компонентам ввода, чтобы они знали, когда для отображения ошибок и цвета, которые должны отображаться в сообщении об ошибке, и т.д. В приведенном выше примере вход дня рождения не получает необходимых реквизитов, поскольку он завернут в компонент Label.

Любой план React.Children.map иметь "рекурсивный" режим или любую другую утилиту, которая может выполнить то, что я пытаюсь сделать?

В конце дня я хотел бы написать одну функцию, которая отображает все дочерние (даже вложенные) для выполнения операции над ней (в этом случае клонирование).

Ответ 1

Хотя это не испечено в React, это, безусловно, возможно:

import React from "react";

function recursiveMap(children, fn) {
  return React.Children.map(children, child => {
    if (!React.isValidElement(child)) {
      return child;
    }

    if (child.props.children) {
      child = React.cloneElement(child, {
        children: recursiveMap(child.props.children, fn)
      });
    }

    return fn(child);
  });
}

Ответ 2

Если вы хотите подталкивать реквизиты в набор детей под своим компонентом "автоматически", вы также можете использовать контекст. Это позволяет вам "давать" свойства, функции и т.д. childContextTypes компонентам, предоставляя им родительский childContextTypes и getChildContext() и предоставляя дочерние "запросы" им с contextTypes.

Ответ 3

Короткий ответ на этот вопрос заключается в том, что в настоящее время это невозможно в отношении Реагирования 0.14. Как упоминал FakeRainBrigand, это, скорее всего, запах кода, так что это должно побуждать к переоценке вашей реализации.

Ответ 4

Я создал небольшую библиотеку для работы с детской структурой. Вы можете проверить это здесь:

https://github.com/fernandopasik/react-children-utilities

В вашем случае вы можете использовать метод deepMap:

import React from 'react';
import Children from 'react-children-utilities';

var newChildren = Children.deepMap(this.props.children, function(child) {
    if (is.inArray(child.type.displayName, supportedInputTypes)) {
      var extraChildProps = {
        alertColor: this.props.alertColor,
        displayErrors: this.state.displayErrors
      }
      return React.cloneElement(child, extraChildProps);
    } else {
      return child;
    }
}.bind(this));

Ответ 5

Этот поток на самом деле пропускает правильный ответ:

const mapRecursive = (children, callback) => (
  React.Children.map(
    children,
    child => (
      child.props.children
        ? [callback(child), mapRecursive(child.props.children, callback)]
        : callback(child)
    ),
  )
);