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

709

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

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

Теги:
react-jsx

20 ответов

775
Лучший ответ

Вы можете использовать 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/

  • 4
    Это не работает для меня. это не определено в React.cloneElement ()
  • 0
    @ Патрик, что не определено? Вы смотрели на работающую скрипку?
Показать ещё 15 комментариев
300

Для более чистого способа сделать это:

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

Примечание: это будет работать, только если есть один дочерний элемент, и это действительный элемент React.

  • 6
    Я использовал самый рейтинговый ответ, но этот гораздо проще! Это решение также используется на странице примеров реакции-маршрутизатора.
  • 8
    Может ли кто-нибудь объяснить, как это работает (или что на самом деле)? Читая документы , я не мог понять, как это сойдет на детей и добавит эту опору каждому ребенку - это то, для чего он предназначен? Если это произойдет, как мы узнаем, что это то, что он будет делать? Совершенно не очевидно, что даже допустимо передавать непрозрачную структуру данных ( this.props.children ) в cloneElement ..., который ожидает ... элемент.
Показать ещё 14 комментариев
67

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

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

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

  • 2
    Можно ли вернуть React.cloneElement() напрямую, не окружая его тегами <div> ? Что, если дочерний элемент является <span> (или чем-то еще), и мы хотим сохранить его тип элемента тега?
  • 0
    Если это один ребенок, вы можете оставить упаковку, и это решение работает только для одного ребенка, так что да.
Показать ещё 4 комментария
46

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

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

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

Контекст предназначен для совместного использования данных, которые можно считать "глобальными" для дерева компонентов 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
    В отличие от принятого ответа, он будет работать правильно, даже если есть другие элементы, включенные в Parent. Это определенно лучший ответ.
  • 4
    Реквизит! = Контекст
Показать ещё 11 комментариев
29

С обновлением до 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>
    );
  }
}
  • 0
    Не забывайте также обновлять react-dom .
17

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

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

5

Вам больше не нужно {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>
  • 2
    Реквизит рендеринга теперь является стандартным в React ( reactjs.org/docs/render-props.html ) и заслуживает рассмотрения в качестве нового принятого ответа на этот вопрос.
  • 10
    Как это ответ на вопрос?
4

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

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
  • 0
    Этот не работает для меня, он дает ошибку: дети не определены.
  • 0
    @Deelux this.props.children вместо детей
Показать ещё 2 комментария
4

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

  • 12
    или просто используйте bind()
  • 1
    или используйте функцию стрелки, которая привязывается к лексической области, я обновил свой ответ
Показать ещё 2 комментария
2

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

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

2

Согласно документации 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>
    )

}
2

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

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

}
1

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

Пример:

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

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}
  • 0
    Это кажется мне более простым (и лучшим по производительности?), Чем принятый ответ.
1

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

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

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

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

0

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

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

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

0

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

    {React.cloneElement(this.props.children, this.props)}
  • 5
    Разве это не копирует this.props.children в this.props.children ребенка? и в действительности копирование ребенка в себя?
  • 0
    jsfiddle.net/reactjs/69z2wepo
Показать ещё 1 комментарий
0

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

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>
  }
})
  • 3
    Нет, я не хочу ограничивать содержимое моей обертки каким-то конкретным содержимым.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню