it-roy-ru.com

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

737
plus-

Вы можете использовать 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 { children } = this.props;

    const childrenWithProps = React.Children.map(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/

756
Dominic

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

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

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

318
Andres F Garcia

Попробуй это

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

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

64
7puns

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

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

Передайте общие, глобальные данные через дерево компонентов через 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>

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

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

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

49
Lyubomir

С обновлением до 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() {
    <Parent>
      <Child value={1} />
      <Child value={2} />
    </Parent>
  }
}

React.createContext светит где React.cloneElement case не может обработать вложенные компоненты

class SomeComponent extends React.Component {

  render() {
    <Parent>
      <Child value={1} />
      <SomeOtherComp><Child value={2} /></SomeOtherComp>
    </Parent>
  }
}
30
Kenneth Truong

Вы можете использовать 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'}) ЗАМЕНЯЕТ ref, так что два родителя по-прежнему не могут получить ссылку на тот же ребенок, если вы не используете callback-refs.

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

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

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

18
Alireza

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

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

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

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

Пример:

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

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

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

4
olenak

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

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

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

}
2
poynting

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

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

2
Alexandr Cherednichenko

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

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

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

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>
1
Ze Rubeus

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

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

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

1
Maksim Kostromin

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

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

Самый лучший способ сделать это:

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

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

0
omeralper

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

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

Почему-то 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
aWebDeveloper