Как передать реквизит {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} для определения компонента-оболочки, как вы передаете какое-либо свойство всем своим дочерним элементам?

Ответ 1

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

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

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

class Parent extends React.PureComponent {
  doSomething = value => {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = React.Children.map(this.props.children, child =>
      React.cloneElement(child, { doSomething: this.doSomething })
    );

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

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

Скрипка: https://jsfiddle.net/2q294y43/2/

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

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

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

class Parent extends React.PureComponent {
  doSomething = value => {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    return <div>{this.props.children(this.doSomething)}</div>
  }
};

ReactDOM.render(
  <Parent>
    {doSomething => (
      <React.Fragment>
        <Child doSomething={doSomething} value="1" />
        <Child doSomething={doSomething} value="2" />
      </React.Fragment>
    )}
  </Parent>,
  document.getElementById('container')
);

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

Скрипки: https://jsfiddle.net/ferahl/y5pcua68/7/

Ответ 2

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

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

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

<div>
    {React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
    {React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>

Ответ 3

Попробуйте это

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

Он работал у меня с помощью реакции-15.1.

Ответ 4

Передайте реквизит, чтобы направить детей.

Посмотреть все остальные ответы

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

Контекст предназначен для совместного использования данных, которые можно считать "глобальными" для дерева компонентов 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>

Все Потребители, которые являются потомками Провайдера, будут выполнять рендеринг всякий раз, когда значение Поставщиков изменяет реквизит. Распространение от провайдера к его потомкам-получателям не зависит от метода shouldComponentUpdate, поэтому получатель обновляется, даже когда компонент-предок выходит из состояния обновления. 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>
    );
  }
}

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

Ответ 5

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

С обновлением до 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>
    );
  }
}

Ответ 6

Вы можете использовать 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, эта новая функция не имеет никакого волшебного встроенного поведения для слияния стиля и className по той же причине, по которой у нас нет этой функции от 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, так как реквизит теперь неизменен. Путь обновления часто заключается в клонировании элемента, но при этом вы можете потерять ссылку. Поэтому нам нужен был более хороший путь обновления здесь. Когда мы обновляли сайты вызовов в Facebook, мы поняли, что нам нужен этот метод. Мы получили такие же отзывы от сообщества. Поэтому мы решили сделать еще один RC до финального релиза, чтобы убедиться, что мы это сделаем.

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

больше здесь...

Ответ 7

Лучший способ, которым вы можете сделать передачу собственности - это children как функция

Пример:

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

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}

Ответ 8

Вам больше не нужно {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>

Ответ 9

Мне нужно было исправить принятый ответ выше, чтобы он работал, используя , который вместо этого указателя. Этот в рамках функции карты не имеет функции 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, что = this

Ответ 10

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

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>

Ответ 11

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

// 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>

}

Ответ 12

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

Ответ 13

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

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

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

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

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

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

Таким образом, cloneElement - это то, что вы бы использовали для предоставления пользовательских реквизитов детям. Тем не менее, в компоненте может быть несколько дочерних элементов, и вам потребуется выполнить цикл по нему. Другие ответы предлагают вам сопоставить их с помощью React.Children.map. Однако React.Children.map отличие от React.cloneElement меняет ключи 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>
    )

}

Ответ 14

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

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

Ответ 15

Если у вас есть несколько детей, которым вы хотите передать реквизиты, вы можете сделать это следующим образом, используя 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>
    );
}

Ответ 16

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

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

Ответ 17

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

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

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

  return children({ doSomething })
}

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

class Child extends React {

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

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

}

Теперь структура fianl будет выглядеть так:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>

Ответ 18

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

    {React.cloneElement(this.props.children, this.props)}

Ответ 19

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

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

Ответ 20

Способ 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>;
};

Ответ 21

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

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

Ответ 22

Почему-то 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. Вы можете передать любую опору подобным образом

Ответ 23

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

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