Когда мне следует использовать React.cloneElement против this.props.children?

118

Я все еще новичок в React, и во многих примерах в Интернете я вижу этот вариант рендеринга дочерних элементов, который меня сбивает с толку. Обычно я вижу это:

class Users extends React.Component {
  render() {
    return (
      <div>
        <h2>Users</h2>
        {this.props.children}
      </div>
    )
  }
}

Но потом я вижу такой пример:

<ReactCSSTransitionGroup
     component="div"
     transitionName="example"
     transitionEnterTimeout={500}
     transitionLeaveTimeout={500}
     >
     {React.cloneElement(this.props.children, {
       key: this.props.location.pathname
      })}
</ReactCSSTransitionGroup>

Теперь я понимаю api, но в документации не совсем ясно, когда мне следует его использовать.

Итак, что делает один, чего не может другой? Может ли кто-нибудь объяснить мне это на лучших примерах?

Амит Эрандол
источник
3
youtube.com/watch?v=hEGg-3pIHlE этот парень показывает, как он использует cloneElement. Посмотрите на это несколько примеров
iurii

Ответы:

69

Редактировать:

Вместо этого посмотрите на ответ Веннесы , который является лучшим объяснением.

Оригинал:

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

Практически все {this.props.children}, что вам нужно. Клонирование полезно в некоторых более сложных сценариях, когда родительский элемент отправляет элемент, а дочерний компонент должен изменить некоторые свойства этого элемента или добавить такие вещи, как ref для доступа к фактическому элементу DOM.

В приведенном выше примере родительский элемент, который передает дочернему элементу, не знает о ключевом требовании для компонента, поэтому он создает копию данного элемента и добавляет ключ на основе некоторого уникального идентификатора в объекте. Для получения дополнительной информации о том, что делает ключ: https://facebook.github.io/react/docs/multiple-components.html

Мортен Олсен
источник
183

props.childrenне настоящие дети; Это descriptorдетище. Так что вам действительно нечего менять; вы не можете изменять реквизиты или редактировать какие-либо функции; только ты можешь read from it. Если вам нужно внести какие-либо изменения, вы должны создать, new elementsиспользуя React.CloneElement.

https://egghead.io/lessons/react-use-react-cloneelement-to-extend-functionality-of-children-components

Пример:

основная функция рендеринга компонента, такого как App.js:

render() {   
    return(
            <Paragraph>
              <Sentence>First</Sentence>
              <Sentence>Second</Sentence>
              <Sentence>Third</Sentence>
            </Paragraph>   
    ) 
}

теперь предположим, что вам нужно добавить onClickкаждый дочерний элемент Paragraph; так что в вашем Paragraph.jsвы можете сделать:

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

тогда просто вы можете сделать это:

render() {   
  return(
        <Paragraph onClick={this.onClick}>
          <Sentence>First</Sentence>
          <Sentence>Second</Sentence>
          <Sentence>Third</Sentence>
        </Paragraph>   
   ) 
}

Примечание:React.Children.map функция будет видеть только top levelэлементы, он не видит какой - либо из вещей , что эти элементы делают; Это означает, что вы предоставляете прямой реквизит детям (здесь <Sentence />элементы). Если вам нужно, чтобы реквизит передавался дальше, скажем, у вас будет <div></div>внутри одного из <Sentence />элементов, который хочет использовать этот onClickреквизит, тогда в этом случае вы можете использовать Context APIдля этого. Сделайте Paragraphпоставщика и Sentenceэлементы потребителями.

Vennesa
источник
11
Это четкий ответ на примере реального мира. Требуется больше голосов.
SeaWarrior404
1
Согласитесь с @ SeaWarrior404 - я предполагаю, что OP искал итерацию по коллекции, а не по одному дочернему элементу, потому что его пользовательский интерфейс имеет заголовок «Пользователи» / множественное число. Использование React.Children.mapв сочетании с, React.cloneElementкак указывает @vennesa, очень эффективно
jakeforaker
23

На самом деле, React.cloneElementстрого не связано с this.props.children.

Это полезно всякий раз, когда вам нужно клонировать элементы response ( PropTypes.element) для добавления / переопределения свойств, не желая, чтобы родитель знал о внутреннем устройстве этих компонентов (например, прикреплении обработчиков событий или назначении key/ refатрибутах).

Также неизменяемы элементы реакции .

React.cloneElement (element, [props], [... children]) почти эквивалентен: <element.type {...element.props} {...props}>{children}</element.type>


Однако childrenопора в React особенно используется для сдерживания (также известной как композиция ), соединения с React.ChildrenAPI, и React.cloneElementкомпонент, который использует props.children, может обрабатывать больше логики (например, переходы состояний, события, измерения DOM и т. Д.) Внутри, передавая часть рендеринга для где бы он ни использовался, React Router <switch/>или составной компонент <select/>- отличные примеры.

И последнее, что стоит упомянуть, это то, что элементы react не ограничиваются props.children.

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

Они могут быть любыми осмысленными реквизитами, ключевым моментом было определение хорошего контракта для компонента, чтобы его потребители могли быть отделены от базовых деталей реализации, независимо от того, использует ли он React.Children, React.cloneElementили даже React.createContext.

Xlee
источник