Как передать реквизит {this.props.children}

Я пытаюсь найти правильный способ определить некоторые компоненты, которые можно было бы использовать в общем виде:

<Parent>
  <Child value="1">
  <Child value="2">
</Parent>

Конечно, существует логика рендеринга между родительскими и дочерними компонентами, вы можете представить <select> и <option> в качестве примера этой логики.

Это фиктивная реализация для решения вопроса:

var Parent = React.createClass({
  doSomething: function(value) {
  },
  render: function() {
    return (<div>{this.props.children}</div>);
  }
});

var Child = React.createClass({
  onClick: function() {
    this.props.doSomething(this.props.value); // doSomething is undefined
  },
  render: function() {
    return (<div onClick={this.onClick}></div>);
  }
});

Вопрос в том, как передать какое-то свойство всем его дочерним элементам, когда вы используете {this.props.children} для определения компонента-оболочки?


person plus-    schedule 03.09.2015    source источник
comment
Я многому научился из ответов на этот вопрос. Я думаю, что Context API - лучшее решение в сегодняшней стране React. Но если вы хотите использовать React.cloneElement, я столкнулся с одной проблемой, связанной с неправильным повторением дочерних элементов с React.Children.map(). Подробнее см. Как передать реквизиты {react.children}   -  person Victor Ofoegbu    schedule 26.05.2021


Ответы (28)


Клонирование детей с новым реквизитом

Вы можете использовать React.Children для перебора дочерних элементов, а затем клонировать каждый элемент с новым props (неглубокое слияние) с использованием React.cloneElement. Например:

const Child = ({ doSomething, value }) => (
  <button onClick={() => doSomething(value)}>Click Me</button>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log("doSomething called by child with value:", value);
  }

  const childrenWithProps = React.Children.map(children, child => {
    // Checking isValidElement is the safe way and avoids a typescript
    // error too.
    if (React.isValidElement(child)) {
      return React.cloneElement(child, { doSomething });
    }
    return child;
  });

  return <div>{childrenWithProps}</div>
}

function App() {
  return (
    <Parent>
      <Child value={1} />
      <Child value={2} />
    </Parent>
  );
}

ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

Вызов детей как функция

Кроме того, вы можете передавать реквизиты детям с помощью рендеринга реквизитов. В этом подходе дочерние элементы (которые могут быть children или любым другим именем опоры) - это функция, которая может принимать любые аргументы, которые вы хотите передать, и возвращает дочерние элементы:

const Child = ({ doSomething, value }) => (
  <button onClick={() => doSomething(value)}>Click Me</button>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log("doSomething called by child with value:", value);
  }

  // Note that children is called as a function and we can pass args to it.
  return <div>{children(doSomething)}</div>
}

function App() {
  // doSomething is the arg we passed in Parent, which
  // we now pass through to Child.
  return (
    <Parent>
      {doSomething => (
        <React.Fragment>
          <Child doSomething={doSomething} value={1} />
          <Child doSomething={doSomething} value={2} />
        </React.Fragment>
      )}
    </Parent>
  );
}

ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

Вместо <React.Fragment> или просто <> вы также можете вернуть массив, если хотите.

person Dominic    schedule 03.09.2015
comment
У меня это не работает. это не определено в React.cloneElement () - person Patrick; 08.03.2016
comment
@ Патрик, что не определено? Вы смотрели на работающую скрипку? - person Dominic; 08.03.2016
comment
Это отлично подходит для передачи свойств, но когда я передаю функцию с обратным вызовом, объект обратного вызова не возвращается. Я делаю что-то не так, или мне нужно сделать что-то другое в этом случае? - person conor909; 25.03.2016
comment
@ conor909 не могли бы вы прислать мне пример того, что вы делаете, с помощью pastebin? - person Dominic; 26.03.2016
comment
@DominicTobias, извини за поздний ответ. Меня не было. Вот jsFiddle jsfiddle.net/conor909/gqdfwg6p - person conor909; 09.04.2016
comment
@ conor909 Np, почему вы используете контекст для вызова myFunction - this.context.myFunction. Трудно понять, что вы пытаетесь сделать без рабочего примера, но вы должны вызывать this.props.myFunction из дочернего элемента - контекст не нужен - person Dominic; 09.04.2016
comment
@DominicTobias Спасибо за ваш вклад и извинения, я не обновил контекст / реквизиты в моем jsFiddle. Я хотел использовать props. Я обновил его сейчас. Я также задал этот вопрос по SO, если вы хотите внести свой вклад: stackoverflow.com/questions/36520479/ - person conor909; 09.04.2016
comment
Я пытаюсь понять, в каких разных ситуациях вам нужно использовать this.props.children? В этом ответе похоже, что вы получаете возможность ловить потомков перед рендерингом из родительского определения и делать что-то с потомками, прежде чем возвращать их для рендеринга. Также в документах упоминаются методы React.children, которые я не вижу в использовании. Сценарии использования документации не кажутся мне интуитивно понятными, и они не только дают пример их передачи, что можно сделать буквально повсюду в приложении, безотносительно к здравомыслию или разумности. - person Urasquirrel; 26.10.2016
comment
Этот ответ не работает, value, переданный в doSomething, теряется. - person Dave; 03.03.2017
comment
@DominicTobias Arg, извините, я переключил console.log на предупреждение и забыл объединить два параметра в одну строку. - person Dave; 04.03.2017
comment
Когда есть только 1 единственный дочерний элемент, this.props.children должен быть синглтоном, а не массивом? Потому что я обнаружил, что когда есть только один дочерний элемент, я могу просто использовать React.cloneElement(this.props.children, ...). - person CMCDragonkai; 09.03.2017
comment
что, если у меня есть нереагирующие элементы в this.props.children, например <div> / </br> и т. д. ??? - person Narasimha Reddy - Geeker; 01.06.2017
comment
Этот ответ был очень полезным, но я столкнулся с проблемой, которая здесь не упоминается, и мне было интересно, изменилось ли это что-то новое или это что-то странное с моей стороны. Когда я клонировал свой дочерний элемент, он был установлен на старый элемент, пока я не добавил this.props.children.props.children к третьему аргументу cloneElement. - person aphenine; 06.07.2017
comment
Что, если дочерний элемент загружается через маршрут (v4), который загружается с отдельной страницы маршрута? - person blamb; 13.03.2018
comment
Проверьте тип своего дочернего элемента, если вы получили ошибку о недопустимом типе элемента. Это отбросило меня на 2 часа отладки, потому что мои дочерние объекты время от времени чередовались между строкой - сверху вниз в приложении - и обычной структурой, подобной массиву. Когда вы клонируете строку, она не кричит на вас, а создает недопустимый элемент реакции. - person Alex D; 14.11.2018
comment
Почему необходимо / лучше / безопаснее клонировать компонент, а не изменять его? Я задал этот вопрос более подробно здесь. - person cglacet; 22.02.2019
comment
Причина отрицательного голоса: в вашем примере - продвижение плохих практик и некачественного кода читателям, настоящий спагетти - person Marwen Trabelsi; 18.06.2019
comment
@MarwenTrabelsi Я демонстрирую технический ответ, а не что-то проектирую. Я бы предпочел использовать renderprops вместо cloneElement. Как вы предлагаете, чтобы все было по-другому? - person Dominic; 18.06.2019
comment
@Marwen Trabelsi, что будет считаться лучшей практикой? - person Robert Molina; 27.08.2019
comment
перестаньте везде использовать стрелку fn - отладчик их не поймает! - только когда это имеет смысл - person Marwen Trabelsi; 27.08.2019
comment
Не связано с вопросом, но нет смысла использовать PureComponent, когда у него есть дочерние элементы, переданные в реквизитах, поскольку он не будет пропускать никаких обновлений. - person Gergő Horváth; 12.09.2019
comment
Извините, это был первый ответ Calling children as a function шаблон stackoverflow.com/a/54534202/10828885 Не могли бы вы напомнить об этом? - person Nick Ovchinnikov; 24.06.2021

Чтобы сделать это немного чище, попробуйте:

<div>
    {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>

Изменить: для использования с несколькими отдельными дочерними элементами (ребенок сам должен быть компонентом), вы можете это сделать. Проверено в 16.8.6

<div>
    {React.cloneElement(this.props.children[0], { loggedIn: true, testPropB: true })}
    {React.cloneElement(this.props.children[1], { loggedIn: true, testPropA: false })}
</div>
person Andres F Garcia    schedule 30.01.2016
comment
Я использовал самый популярный ответ, но этот намного проще! Это решение также используется на странице примеров реактивного маршрутизатора. - person captDaylight; 09.03.2016
comment
Может ли кто-нибудь объяснить, как это работает (или что на самом деле)? Читая документы, я не мог посмотрите, как это перейдет к детям и добавит эту опору каждому ребенку - это то, для чего он предназначен? Если да, то как мы узнаем, что он будет делать именно это? Совершенно не очевидно, что даже допустимо передавать непрозрачную структуру данных (this.props.children) в cloneElement ... который ожидает ... элемент. - person GreenAsJade; 29.03.2016
comment
Точно, похоже, это не работает с более чем одним ребенком. - person Danita; 29.03.2016
comment
Да, я был удивлен, что это сработало для людей, я бы подумал, что на самом деле это вызовет ошибку. - person Dominic; 29.03.2016
comment
Это работает, потому что this.props.children - это просто компонент React, когда есть только один дочерний элемент: facebook.github.io/react/tips/children-props-type.html - person ericsoco; 19.04.2016
comment
Таким образом, вы можете написать код, который работает, пока кто-то передает в компонент только одного дочернего элемента, но когда они добавляют еще один, он дает сбой ... это звучит не очень хорошо на первый взгляд? Похоже, это ловушка для ОП, который конкретно спросил о передаче реквизита всем потомкам. - person GreenAsJade; 05.05.2016
comment
@GreenAsJade - все в порядке, если ваш компонент ожидает одного дочернего элемента. Вы можете определить через свои компоненты propTypes, что он ожидает одного дочернего элемента. React.Children.only функция возвращает единственного потомка или выдает исключение, если их несколько (этого не существовало бы, если бы не было варианта использования). - person cchamberlain; 05.05.2016
comment
Отлично - propTypes определенно звучит как лучший вариант! - person GreenAsJade; 06.05.2016
comment
@Danita Если это не работает для вас, и вы столкнулись с ошибкой, например: Uncaught TypeError: Cannot read property 'props' of null попробуйте обновить свой код до {this.props.children && React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}. Сначала проверяется наличие дочерних элементов и выполняется только cloneElement, если дети существуют. - person sma; 25.10.2016
comment
Что, если мне нужно добавить ref опору? - person Bohdan Lyzanets; 05.01.2017
comment
Этот метод следует комбинировать с типами свойств, которые указывают, что разрешен / требуется только один child: myComponent.propTypes = { children: React.PropTypes.element.isRequired }. Как определено здесь: facebook.github.io/ реагировать / docs /. PropTypes.node указывает одного или нескольких дочерних элементов, где PropTypes.element указывает ровно 1 элемент. - person s.meijer; 21.03.2017
comment
Проголосовали против. Он не отвечает на вопрос, как передать какое-либо свойство всем дочерним элементам ?. В нем даже не упоминается тот факт, что он выйдет из строя и сгорит, если вы попытаетесь использовать его для более чем одного ребенка. Как 173 человека посчитали это хорошим ответом? - person Søren Boisen; 06.06.2017
comment
Проголосовали против по той же причине, о которой упоминал @ Søren Boisen. По крайней мере, отредактируйте свой ответ и включите тот факт, что вы должны убедиться, что в ваших реквизитах есть только дочерний элемент, например проверяю это в propTypes. - person trixn; 08.07.2017
comment
будет ли это замедлять рендеринг? - person Dane; 18.04.2018
comment
Этот ответ очень ясно демонстрирует, почему команда React приняла неправильное решение, чтобы this.props.children (обратите внимание на множественное число!) Был самим единственным объектом, когда есть только один дочерний объект. Это означает, что вы никогда не можете рассматривать this.props.children как обычный массив, потому что на самом деле это может не быть массив. Отсюда и все помощники по работе с детьми. В Preact они решили просто всегда использовать массив, и это значительно упростило жизнь. Кроме того, в ES6 наличие функций типа cloneElement для создания неглубокой копии является излишним: var copy = { ... original } - это все, что вам нужно. - person Stijn de Witt; 13.11.2018
comment
React.Children.toArray() reactjs.org/docs/react-api.html#reactchildrentoarray - person Mark Swardstrom; 03.09.2019
comment
Спасибо, чувак, отличный ответ, это сделало то, что я искал! - person Tellisense; 05.10.2020
comment
Ради бога, я не могу заставить это работать! Я установил {myProp: () => console.log("damn prop")}, уже несколько часов пробую этот и другие ответы, и никто не работает; /. Если я регистрирую children, я вижу, что он установлен, но на дочернем компоненте, а его там нет. Ба. - person Caio Mar; 28.11.2020

Попробуй это

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

У меня это сработало, используя response-15.1.

person 7puns    schedule 06.06.2016
comment
Можно ли вернуть React.cloneElement() напрямую, не заключая его в теги <div>? Потому что что, если дочерний элемент - это <span> (или что-то еще), и мы хотим сохранить его тип элемента тега? - person adrianmc; 17.12.2016
comment
Если это один ребенок, вы можете не использовать обертку, и это решение работает только для одного ребенка, так что да. - person ThaJay; 15.03.2017
comment
Работает для меня. Без включения ‹div› это нормально. - person Crash Override; 08.05.2017
comment
Если вам нужно явно указать, что вы получаете только одного дочернего элемента, вы можете сделать React.cloneElement(React.Children.only(this.props.children), {...this.props}), который выдаст ошибку, если ему будет передано более одного дочернего элемента. Тогда вам не нужно оборачивать div. - person itsananderson; 22.08.2017
comment
@adrianmc теперь вы можете использовать ‹React.Fragment› вместо div - person Gonzalo.-; 28.03.2018
comment
Этот ответ может привести к ошибке TypeError: циклическое значение объекта. Если вы не хотите, чтобы один из реквизитов ребенка был самим собой, используйте let {children, ...acyclicalProps} = this.props, а затем React.cloneElement(React.Children.only(children), acyclicalProps). - person Parabolord; 16.06.2018
comment
можно ли это использовать с функциональным компонентом? {React.cloneElement(props.children, { ...props })} не работает - person Saba Ahang; 01.04.2021

Передайте реквизит прямым детям.

Посмотреть все другие ответы

Передача общих глобальных данных через дерево компонентов через context

Контекст предназначен для обмена данными, которые можно считать «глобальными» для дерева компонентов React, таких как текущий аутентифицированный пользователь, тема или предпочтительный язык. 1

Отказ от ответственности: это обновленный ответ, в предыдущем ответе использовался старый контекстный API

Он основан на принципе "потребитель / поставщик". Сначала создайте свой контекст

const { Provider, Consumer } = React.createContext(defaultValue);

Затем используйте через

<Provider value={/* some value */}>
  {children} /* potential consumers */
</Provider>

а также

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

Все потребители, являющиеся потомками провайдера, будут повторно отображаться при изменении значения свойства провайдера. Распространение от Provider к его потомкам Consumers не подлежит методу shouldComponentUpdate, поэтому Consumer обновляется, даже когда компонент-предок выходит из обновления. 1

Полный пример, полу-псевдокод.

import React from 'react';

const { Provider, Consumer } = React.createContext({ color: 'white' });

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { color: 'black' },
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

class Toolbar extends React.Component {
  render() {
    return ( 
      <div>
        <p> Consumer can be arbitrary levels deep </p>
        <Consumer> 
          {value => <p> The toolbar will be in color {value.color} </p>}
        </Consumer>
      </div>
    );
  }
}

1 https://facebook.github.io/react/docs/context.html

person Lyubomir    schedule 08.09.2016
comment
В отличие от принятого ответа, это будет работать правильно, даже если в родительский элемент включены другие элементы. Это определенно лучший ответ. - person Zaptree; 12.09.2016
comment
Реквизит! = Контекст - person Petr Peller; 11.12.2016
comment
вы не можете зависеть от изменений, распространяющихся через контекст. Используйте реквизит, когда он может измениться. - person ThaJay; 15.03.2017
comment
Может быть, я не понимаю, но разве нельзя сказать, что контекст делает доступными реквизиты? Когда я в последний раз использовал контекст, это была отдельная вещь (например, _1 _) - она ​​не волшебным образом объединяла контекст с реквизитами. Вы должны были намеренно установить и использовать контекст, а это совсем другое. - person Josh; 21.04.2017
comment
Вы прекрасно понимаете, это было неверно. Я отредактировал свой ответ. - person Lyubomir; 21.04.2017
comment
@ThaJay нет, но вы можете передать функцию subscribeToX вниз через контекст и вызвать ее в потомках, чтобы подписаться на изменения в X. - person Andy; 02.11.2017
comment
@MatthewHerbst Я не думаю, что использование context действительно единственный вариант. Лучше ободрять, чем обескураживать - я бы сказал, что контекст может быть чрезвычайно удобным - просто узнайте о нем достаточно, чтобы знать о подводных камнях и о том, когда его не использовать. - person Andy; 02.11.2017
comment
@ThaJay также, response-router v4 на самом деле (несколько спорно) полагается на передачу изменяемых свойств вниз через контекст. В их документах есть явные инструкции о том, как не допустить, чтобы чистые компоненты поглотили изменения опоры, заключив их в withRouter. - person Andy; 02.11.2017
comment
@Andy не то же самое, но действительно работает. Функция статична и определяется только один раз. Посмотрите на MobX хороший пример, который следует этой мысли (альтернатива изменяемому сокращению). - person ThaJay; 23.02.2018
comment
@ThaJay Я знаю о MobX, но ничего не знаю о том, как он использует контекст, может быть, у вас есть ссылка? - person Andy; 23.02.2018
comment
Мне нужно отредактировать этот ответ, потому что у них есть новый контекстный API, но сейчас я пишу свою диссертацию. Скоро :-) - person Lyubomir; 24.02.2018
comment
@Andy от самого автора MobX medium.com/@ mweststrate / - person ThaJay; 27.02.2018

Передача реквизита вложенным потомкам

После обновления до React 16.6 теперь вы можете использовать React.createContext и contextType.

import * as React from 'react';

// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext(); 

class Parent extends React.Component {
  doSomething = (value) => {
    // Do something here with value
  };

  render() {
    return (
       <MyContext.Provider value={{ doSomething: this.doSomething }}>
         {this.props.children}
       </MyContext.Provider>
    );
  }
}

class Child extends React.Component {
  static contextType = MyContext;

  onClick = () => {
    this.context.doSomething(this.props.value);
  };      

  render() {
    return (
      <div onClick={this.onClick}>{this.props.value}</div>
    );
  }
}


// Example of using Parent and Child

import * as React from 'react';

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <Child value={2} />
      </Parent>
    );
  }
}

React.createContext показывает, где случай React.cloneElement не может обрабатывать вложенные компоненты.

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <SomeOtherComp><Child value={2} /></SomeOtherComp>
      </Parent>
    );
  }
}
person Kenneth Truong    schedule 09.04.2018
comment
Вы можете объяснить, почему = ›функции - плохая практика? Функция = ›помогает связать обработчики событий для получения this контекста. - person Kenneth Truong; 18.06.2019
comment
@KennethTruong, потому что каждый раз при рендеринге он создает функцию. - person itdoesntwork; 11.07.2019
comment
@itdoesntwork это неправда. Он создает новую функцию только при создании класса. Он не создается во время функции рендеринга. - person Kenneth Truong; 11.07.2019
comment
@KennethTruong reactjs.org/docs/faq-functions.html# arrow-function-in-render Я думал, вы говорите о стрелочной функции в рендере. - person itdoesntwork; 12.07.2019

Лучший способ, который позволяет вам осуществить передачу собственности, - это children как шаблон функции https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9

Фрагмент кода: https://stackblitz.com/edit/react-fcmubc

Пример:

const Parent = ({ children }) => {
    const somePropsHere = {
      style: {
        color: "red"
      }
      // any other props here...
    }
    return children(somePropsHere)
}

const ChildComponent = props => <h1 {...props}>Hello world!</h1>

const App = () => {
  return (
    <Parent>
      {props => (
        <ChildComponent {...props}>
          Bla-bla-bla
        </ChildComponent>
      )}
    </Parent>
  )
}

person Nick Ovchinnikov    schedule 05.02.2019
comment
Мне это кажется более простым (и лучше для производительности?), Чем принятый ответ. - person Shikyo; 03.03.2019
comment
Это требует, чтобы дочерние элементы были функцией, и не работает для вложенных компонентов. - person digital illusion; 30.04.2019
comment
@digitalillusion, я не понимаю, что это значит nested components. В React нет вложенных паттернов, только композиции. Да, дочерние элементы должны быть функцией, никаких конфликтов нет, потому что это действительный дочерний элемент JSX. Можете привести пример с nesting components? - person Nick Ovchinnikov; 01.05.2019
comment
Вы правы, с глубоко вложенными дочерними элементами тоже можно справиться <Parent>{props => <Nest><ChildComponent /></Nest>}</Parent> вместо (не работает) <Parent><Nest>{props => <ChildComponent />}</Nest></Parent>, поэтому я согласен, что это лучший ответ - person digital illusion; 02.05.2019
comment
При попытке я получаю следующее: TypeError: children is not a function - person Ryan Prentiss; 30.10.2019
comment
@RyanPrentiss Я бы посмотрел на ваш код. Вы можете поделиться каким-нибудь фрагментом? - person Nick Ovchinnikov; 01.11.2019
comment
@RyanPrentiss У меня такая же ошибка, ты понял? - person ekkis; 05.06.2020
comment
Фрагмент кода @ekkis, возможно, он поможет stackblitz.com/edit/react-fcmubc - person Nick Ovchinnikov; 22.07.2020

Вы можете использовать React.cloneElement, лучше узнать, как он работает, прежде чем начинать использовать его в своем приложении. Он представлен в React v0.13, читайте дополнительную информацию, так что кое-что вместе с этой работой для вас:

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

Так что принесите строки из документации React, чтобы вы поняли, как все это работает и как вы можете их использовать:

В React v0.13 RC2 мы представим новый API, похожий на React.addons.cloneWithProps, с такой сигнатурой:

React.cloneElement(element, props, ...children);

В отличие от cloneWithProps, эта новая функция не имеет встроенного волшебного поведения для слияния стиля и имени класса по той же причине, по которой у нас нет этой функции из transferPropsTo. Никто не знает, каков именно полный список волшебных вещей, что затрудняет рассуждение о коде и затрудняет повторное использование, когда стиль имеет другую подпись (например, в грядущем React Native).

React.cloneElement почти эквивалентен:

<element.type {...element.props} {...props}>{children}</element.type>

Однако, в отличие от JSX и cloneWithProps, он также сохраняет ссылки. Это означает, что если у вас будет ребенок со ссылкой, вы не украдете его случайно у своего предка. Вы получите такой же реф, прикрепленный к вашему новому элементу.

Один из распространенных способов - нанести на карту ваших детей и добавить новую опору. Сообщалось о многих проблемах, связанных с потерей ссылки cloneWithProps, что затрудняло рассуждение о вашем коде. Теперь, следуя тому же шаблону с cloneElement, будет работать, как ожидалось. Например:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

Примечание. React.cloneElement (child, {ref: 'newRef'}) ДЕЙСТВИТЕЛЬНО переопределяет ссылку, поэтому два родителя по-прежнему не могут иметь ссылку на одного и того же дочернего элемента, если вы не используете callback-refs.

Это была критическая функция для внедрения в React 0.13, поскольку свойства теперь неизменяемы. Путь обновления часто заключается в клонировании элемента, но при этом вы можете потерять ссылку. Следовательно, здесь нам нужен был более удобный способ обновления. Когда мы обновляли call-сайты в Facebook, мы поняли, что нам нужен этот метод. Мы получили такие же отзывы от сообщества. Поэтому мы решили сделать еще один RC перед финальным выпуском, чтобы убедиться, что мы это получим.

Мы планируем в конечном итоге отказаться от React.addons.cloneWithProps. Мы пока этого не делаем, но это хорошая возможность подумать о собственном использовании и подумать об использовании вместо него React.cloneElement. Мы обязательно отправим выпуск с уведомлениями об устаревании, прежде чем мы действительно удалим его, поэтому никаких немедленных действий не требуется.

подробнее здесь ...

person Alireza    schedule 12.06.2017

Мне нужно было исправить принятый выше ответ, чтобы он работал с использованием этого вместо указателя this. Это в рамках функции карты не имеет определенной функции doSomething.

var Parent = React.createClass({
doSomething: function() {
    console.log('doSomething!');
},

render: function() {
    var that = this;
    var childrenWithProps = React.Children.map(this.props.children, function(child) {
        return React.cloneElement(child, { doSomething: that.doSomething });
    });

    return <div>{childrenWithProps}</div>
}})

Обновление: это исправление для ECMAScript 5, в ES6 нет необходимости в var that = this

person olenak    schedule 29.12.2015
comment
или просто используйте bind() - person plus-; 04.01.2016
comment
или используйте стрелочную функцию, которая привязана к лексической области, я обновил свой ответ - person Dominic; 22.01.2016
comment
что, если doSomething взял объект, например doSomething: function(obj) { console.log(obj) }, а в Child вы бы позвонили this.props.doSomething(obj), чтобы выйти "obj" - person conor909; 10.04.2016
comment
@ plus- я знаю, что это старый, но использование bind здесь ужасная идея, bind создает новую функцию, которая связывает контекст с новым. в основном функция, вызывающая метод apply. использование bind() в функции рендеринга будет создавать новую функцию при каждом вызове метода рендеринга. - person Bamieh; 20.11.2016

Ни один из ответов не касается проблемы наличия дочерних элементов, которые НЕ являются компонентами React, например текстовыми строками. Обходной путь может быть примерно таким:

// Render method of Parent component
render(){
    let props = {
        setAlert : () => {alert("It works")}
    };
    let childrenWithProps = React.Children.map( this.props.children, function(child) {
        if (React.isValidElement(child)){
            return React.cloneElement(child, props);
        }
          return child;
      });
    return <div>{childrenWithProps}</div>

}
person poynting    schedule 16.10.2017

Более чистый способ рассмотрения одного или нескольких детей

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
person and_rest    schedule 24.11.2016
comment
У меня это не работает, выдает ошибку: дети не определены. - person Deelux; 27.11.2016
comment
@Deelux this.props.children вместо детей - person Martin Dawson; 15.12.2016
comment
это передает дочерний элемент как его собственный дочерний элемент в this.props. В общем, я бы рекомендовал клонирование только с определенными реквизитами, а не целиком. - person Andy; 02.11.2017
comment
Передача {...this.props} не сработала для меня, правильный ли способ {...child.props}? - person Felipe Augusto; 06.07.2018
comment
Для функциональных компонентов: React.Children.map(children, child => React.cloneElement(child, props)) - person vsync; 02.06.2020

Если у вас несколько детей, которым вы хотите передавать реквизиты, вы можете сделать это таким образом, используя React.Children.map:

render() {
    let updatedChildren = React.Children.map(this.props.children,
        (child) => {
            return React.cloneElement(child, { newProp: newProp });
        });

    return (
        <div>
            { updatedChildren }
        </div>
    );
}

Если у вашего компонента есть только один дочерний элемент, нет необходимости в сопоставлении, вы можете сразу же cloneElement:

render() {
    return (
        <div>
            {
                React.cloneElement(this.props.children, {
                    newProp: newProp
                })
            }
        </div>
    );
}
person Nesha Zoric    schedule 01.03.2018

Parent.jsx:

import React from 'react';

const doSomething = value => {};

const Parent = props => (
  <div>
    {
      !props || !props.children 
        ? <div>Loading... (required at least one child)</div>
        : !props.children.length 
            ? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
            : props.children.map((child, key) => 
              React.cloneElement(child, {...props, key, doSomething}))
    }
  </div>
);

Child.jsx:

import React from 'react';

/* but better import doSomething right here,
   or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}/>
);

и main.jsx:

import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';

render(
  <Parent>
    <Child/>
    <Child value='1'/>
    <Child value='2'/>
  </Parent>,
  document.getElementById('...')
);

см. пример здесь: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview

person Maksim Kostromin    schedule 04.11.2016

Вам больше не нужен {this.props.children}. Теперь вы можете обернуть дочерний компонент, используя render в Route, и передать свои реквизиты как обычно:

<BrowserRouter>
  <div>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/posts">Posts</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

    <hr/>

    <Route path="/" exact component={Home} />
    <Route path="/posts" render={() => (
      <Posts
        value1={1}
        value2={2}
        data={this.state.data}
      />
    )} />
    <Route path="/about" component={About} />
  </div>
</BrowserRouter>
person yeasayer    schedule 02.11.2016
comment
Свойства рендеринга теперь являются стандартными в React (reactjs.org/docs/render-props.html) и заслуживают рассмотрения как новый общепринятый ответ на этот вопрос. - person Ian Danforth; 18.04.2018
comment
Как это ответ на вопрос? - person Maximo Dominguez; 16.05.2018
comment
Да ... это не ответ на вопрос, потому что вопрос не о реактивном маршрутизаторе. Однако он ответил на мой вопрос, связанный с этим вопросом, относящимся к response-router. Эта основная информация нигде не ясна на веб-сайте реактивного маршрутизатора, который я могу найти. Конечно, не упоминается в их примечаниях к обновлению (которые ужасно неполны). Этот ответ следует переместить в отдельный вопрос. - person juanitogan; 21.07.2020

Меня вдохновили все приведенные выше ответы, и я сделал это. Я передаю некоторые реквизиты, такие как некоторые данные, и некоторые компоненты.

import React from "react";

const Parent = ({ children }) => {
  const { setCheckoutData } = actions.shop;
  const { Input, FieldError } = libraries.theme.components.forms;

  const onSubmit = (data) => {
    setCheckoutData(data);
  };

  const childrenWithProps = React.Children.map(
    children,
    (child) =>
      React.cloneElement(child, {
        Input: Input,
        FieldError: FieldError,
        onSubmit: onSubmit,
      })
  );

  return <>{childrenWithProps}</>;
};

person DonKoko    schedule 26.08.2020

В дополнение к ответу @and_rest, вот как я клонирую детей и добавляю класс.

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
person sidonaldson    schedule 02.02.2017

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

Функция как дочерние компоненты

person Alexandr Cherednichenko    schedule 10.02.2018

Я думаю, что рендеринг - подходящий способ справиться с этим сценарием.

Вы позволяете Родителю предоставить необходимые реквизиты, используемые в дочернем компоненте, путем рефакторинга родительского кода, чтобы он выглядел примерно так:

const Parent = ({children}) => {
  const doSomething(value) => {}

  return children({ doSomething })
}

Затем в дочернем компоненте вы можете получить доступ к функции, предоставленной родителем, следующим образом:

class Child extends React {

  onClick() => { this.props.doSomething }

  render() { 
    return (<div onClick={this.onClick}></div>);
  }

}

Теперь конструкция невесты будет выглядеть так:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>
person ZEE    schedule 19.04.2019
comment
что, если родительский элемент - это просто оболочка для {children}, которая является текстовой областью в другом компоненте класса? - person Adebayo; 04.06.2020

Согласно документации cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

Клонируйте и возвращайте новый элемент React, используя element в качестве отправной точки. Результирующий элемент будет иметь исходные реквизиты элемента с неглубоко объединенными новыми реквизитами. Новые дети заменят существующих детей. key и ref из исходного элемента будут сохранены.

React.cloneElement() почти эквивалентен:

<element.type {...element.props} {...props}>{children}</element.type>

Однако он также сохраняет ссылки. Это означает, что если у вас будет ребенок с референсом, вы не украдете его случайно у своего предка. Вы получите такой же реф, прикрепленный к вашему новому элементу.

Итак, cloneElement - это то, что вы могли бы использовать для предоставления настраиваемых свойств детям. Однако в компоненте может быть несколько дочерних элементов, и вам нужно будет перебрать его. Другие ответы предлагают вам сопоставить их с помощью React.Children.map. Однако React.Children.map в отличие от React.cloneElement меняет ключи добавления элемента и дополнительного .$ в качестве префикса. Для получения дополнительных сведений проверьте этот вопрос: React.cloneElement внутри React.Children.map вызывает изменение ключей элементов

Если вы хотите избежать этого, вам следует вместо этого использовать функцию forEach, например

render() {
    const newElements = [];
    React.Children.forEach(this.props.children, 
              child => newElements.push(
                 React.cloneElement(
                   child, 
                   {...this.props, ...customProps}
                )
              )
    )
    return (
        <div>{newElements}</div>
    )

}
person Shubham Khatri    schedule 11.01.2018

Это должно сделать любой, у кого есть единственный дочерний элемент.

{React.isValidElement(this.props.children)
                  ? React.cloneElement(this.props.children, {
                      ...prop_you_want_to_pass
                    })
                  : null}
person user10884362    schedule 08.01.2019

Метод 1 - клонировать детей

const Parent = (props) => {
   const attributeToAddOrReplace= "Some Value"
   const childrenWithAdjustedProps = React.Children.map(props.children, child =>
      React.cloneElement(child, { attributeToAddOrReplace})
   );

   return <div>{childrenWithAdjustedProps }</div>
}

Метод 2 - используйте составной контекст

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

Контекст имеет недостатки:

  1. Данные не передаются обычным образом - через реквизиты.
  2. Использование контекста создает договор между потребителем и поставщиком. Может быть сложнее понять и воспроизвести требования, необходимые для повторного использования компонента.

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

export const Context = createContext<any>(null);

export const ComposableContext = ({ children, ...otherProps }:{children:ReactNode, [x:string]:any}) => {
    const context = useContext(Context)
    return(
      <Context.Provider {...context} value={{...context, ...otherProps}}>{children}</Context.Provider>
    );
}

function App() {
  return (
      <Provider1>
            <Provider2> 
                <Displayer />
            </Provider2>
      </Provider1>
  );
}

const Provider1 =({children}:{children:ReactNode}) => (
    <ComposableContext greeting="Hello">{children}</ComposableContext>
)

const Provider2 =({children}:{children:ReactNode}) => (
    <ComposableContext name="world">{children}</ComposableContext>
)

const Displayer = () => {
  const context = useContext(Context);
  return <div>{context.greeting}, {context.name}</div>;
};

person Ben Carp    schedule 23.06.2019
comment
Немного поздно, но не могли бы вы объяснить обозначения в {children}:{children:ReactNode}? - person camille; 21.03.2020
comment
@camille, это машинописная штука. Глядя на это сейчас, я бы просто ответил с помощью Javascript, и даже если бы я написал Typescript, я бы сделал это по-другому. Возможно, в будущем отредактируем. - person Ben Carp; 22.03.2020
comment
@camille, в основном это означает, что значение с ключом "children" имеет тип ReactNode - person Ben Carp; 22.03.2020

Самый простой способ сделать это:

    {React.cloneElement(this.props.children, this.props)}
person nitte93user3232918    schedule 06.07.2016
comment
Разве это не копирует this.props.children в this.props.children ребенка? а по сути копирование ребенка в себя? - person Arshabh Agarwal; 10.09.2016

При использовании функциональных компонентов вы часто будете получать TypeError: Cannot add property myNewProp, object is not extensible ошибку при попытке установить новые свойства на props.children. Это можно обойти, клонируя реквизиты, а затем клонируя самого ребенка с новыми реквизитами.

const MyParentComponent = (props) => {
  return (
    <div className='whatever'>
      {props.children.map((child) => {
        const newProps = { ...child.props }
        // set new props here on newProps
        newProps.myNewProp = 'something'
        const preparedChild = { ...child, props: newProps }
        return preparedChild
      })}
    </div>
  )
}
person amaster    schedule 24.04.2020

Я пришел к этой публикации, когда искал аналогичную потребность, но мне показалось, что решение для клонирования, которое так популярно, слишком сырое и отвлекает меня от функциональности.

Я нашел статью в реакционных документах Компоненты высшего порядка

Вот мой образец:

import React from 'react';

const withForm = (ViewComponent) => {
    return (props) => {

        const myParam = "Custom param";

        return (
            <>
                <div style={{border:"2px solid black", margin:"10px"}}>
                    <div>this is poc form</div>
                    <div>
                        <ViewComponent myParam={myParam} {...props}></ViewComponent>
                    </div>
                </div>
            </>
        )
    }
}

export default withForm;


const pocQuickView = (props) => {
    return (
        <div style={{border:"1px solid grey"}}>
            <div>this is poc quick view and it is meant to show when mouse hovers over a link</div>
        </div>
    )
}

export default withForm(pocQuickView);

Для меня я нашел гибкое решение в реализации шаблона компонентов высшего порядка.

Конечно, это зависит от функциональности, но хорошо, если кто-то другой ищет аналогичное требование, это намного лучше, чем зависеть от кода реакции исходного уровня, такого как клонирование.

Другой шаблон, который я активно использую, - это шаблон контейнера. читайте об этом, там много статей.

person Kalpesh Popat    schedule 13.08.2020

Это то, что вам нужно?

var Parent = React.createClass({
  doSomething: function(value) {
  }
  render: function() {
    return  <div>
              <Child doSome={this.doSomething} />
            </div>
  }
})

var Child = React.createClass({
  onClick:function() {
    this.props.doSome(value); // doSomething is undefined
  },  
  render: function() {
    return  <div onClick={this.onClick}></div>
  }
})
person amit_183    schedule 03.09.2015
comment
Нет, я не хочу ограничивать содержимое моей оболочки каким-то конкретным содержимым. - person plus-; 03.09.2015

Почему-то у меня не работал React.children. Это то, что сработало для меня.

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

 var newChildren = this.props.children.map((child) => {
 const className = "MenuTooltip-item " + child.props.className;
    return React.cloneElement(child, { className });
 });

 return <div>{newChildren}</div>;

Уловка здесь - React.cloneElement. Аналогичным образом можно передать любую опору

person aWebDeveloper    schedule 27.12.2017

Render props - наиболее точный подход к этой проблеме. Вместо того, чтобы передавать дочерний компонент родительскому компоненту в качестве дочерних свойств, разрешите родительскому компоненту визуализировать дочерний компонент вручную. Render - это встроенная функция в react, которая принимает параметр функции. В этой функции вы можете позволить родительскому компоненту отображать все, что вы хотите, с настраиваемыми параметрами. По сути, он делает то же самое, что и дочерний реквизит, но более настраиваемый.

class Child extends React.Component {
  render() {
    return <div className="Child">
      Child
      <p onClick={this.props.doSomething}>Click me</p>
           {this.props.a}
    </div>;
  }
}

class Parent extends React.Component {
  doSomething(){
   alert("Parent talks"); 
  }

  render() {
    return <div className="Parent">
      Parent
      {this.props.render({
        anythingToPassChildren:1, 
        doSomething: this.doSomething})}
    </div>;
  }
}

class Application extends React.Component {
  render() {
    return <div>
      <Parent render={
          props => <Child {...props} />
        }/>
    </div>;
  }
}

Пример в codepen

person omeralper    schedule 20.02.2019

Это w.r.t. React v17.x ...

Используйте children как функцию и передайте ей реквизиты как шаблон render props, как показано ниже: -

 <ParentComponent {...anyAdditionalProps}>
   {
     (actualPropsToPass) => <ChildComponent>{children(actualPropsToPass)}</ChildComponent>
   }
 </ParentComponent>

Просто убедитесь, что фактический, который будет проецироваться, контент должен быть добавлен как функция в шаблоне рендеринга реквизита, чтобы учесть аргумент, переданный как prop в дочерней функции.

person nimpossible    schedule 12.03.2021

Есть много способов сделать это.

Вы можете передать дочерние элементы в качестве реквизита в родительском элементе.

пример 1:

function Parent({ChildElement}){
   return <ChildElement propName={propValue} />
}

return <Parent ChildElement={ChildComponent}/>

Передать дочерние элементы как функцию

пример 2:

function Parent({children}){
   return children({className: "my_div"})
}

OR

function Parent({children}){
   let Child = children
   return <Child className='my_div' />
}

function Child(props){
  return <div {...props}></div>
}

export <Parent>{props => <Child {...props} />}</Parent>
person Vivek sharma    schedule 30.06.2021