React.js: перенос одного компонента в другой

187

Многие языки шаблонов имеют операторы «slots» или «yield», которые позволяют выполнять какое-то обращение управления, чтобы обернуть один шаблон внутри другого.

Angular имеет опцию «transclude» .

У Rails есть заявление о выходе . Если бы в React.js был оператор yield, он бы выглядел так:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

Желаемый вывод:

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

Увы, React.js не имеет <yield/>. Как определить компонент Wrapper для достижения того же результата?

NVI
источник

Ответы:

246

Пытаться:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          {this.props.children}
        after
      </div>
    );
  }
});

Посмотрите Многократные Компоненты: Дети и Тип реквизита Детей в документах для получения дополнительной информации.

Софи Альперт
источник
8
Или вы можете использовать Компонент высшего порядка :) stackoverflow.com/a/31564812/82609
Себастьян Лорбер
159

С помощью children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

Это также известно как transclusionв Angular.

childrenявляется специальной опорой в React и будет содержать то, что находится внутри тегов вашего компонента (здесь <App name={name}/>внутри Wrapper, так что этоchildren

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

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

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


сделать реквизит

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

Этот шаблон не предназначен для макета. Компонент-обертка обычно используется для хранения и управления некоторым состоянием и добавления его в функции рендеринга.

Встречный пример:

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

Вы можете получить еще больше фантазии и даже предоставить объект

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

Обратите внимание, что вам не обязательно использовать children, это вопрос вкуса / API.

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

На сегодняшний день многие библиотеки используют реквизит рендеринга (контекст React, React-motion, Apollo ...), потому что люди склонны находить этот API проще, чем HOC. response-powerplug - это набор простых компонентов render-prop. Реакция-принятие помогает вам сделать композицию.


Компоненты высшего порядка (HOC).

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

Higher-Order Компонент / HOC , как правило , функция , которая принимает компонент и возвращает новый компонент.

Использование компонента высшего порядка может быть более производительным, чем использование childrenили render props, потому что оболочка может иметь возможность замкнуть рендеринг на шаг впереди shouldComponentUpdate.

Здесь мы используем PureComponent. При повторном рендеринге приложения, если WrappedAppимя prop не изменяется со временем, оболочка может сказать: «Мне не нужно рендерить, потому что реквизиты (фактически, имя) такие же, как и раньше». При использовании childrenприведенного выше решения, даже если это оболочка PureComponent, это не так, потому что дочерний элемент воссоздается каждый раз, когда создается родительский элемент, что означает, что оболочка, вероятно, всегда будет повторно отображаться, даже если упакованный компонент будет чистым. Существует плагин Babel, который может помочь смягчить это и обеспечить постоянный childrenэлемент с течением времени.


Вывод

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

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

Redux сначала использовал оболочку времени выполнения, <Connect>а затем переключился на HOC connect(options)(Comp)по соображениям производительности (по умолчанию оболочка чистая и используется shouldComponentUpdate). Это отличная иллюстрация того, что я хотел выделить в этом ответе.

Обратите внимание, что если у компонента есть API рендеринга-пропа, поверх него обычно легко создать HOC, поэтому, если вы являетесь автором lib, вы должны сначала написать API-интерфейс рендеринга и в конечном итоге предложить версию HOC. Это то, что Apollo делает с <Query>компонентом render-prop и с graphqlпомощью HOC.

Лично я использую оба, но когда сомневаюсь, я предпочитаю HOC, потому что:

  • Составлять их ( compose(hoc1,hoc2)(Comp)) более идиоматично по сравнению с реквизитами рендеринга
  • Это может дать мне лучшие показатели
  • Я знаком с этим стилем программирования

Я без колебаний использую / создаю HOC версии моих любимых инструментов:

  • Реакт Context.Consumerкомп
  • неустановленные-х Subscribe
  • используя graphqlHOC Аполлона вместо Queryрендера проп

На мой взгляд, иногда реквизит рендеринга делает код более читабельным, иногда меньше ... Я стараюсь использовать наиболее прагматичное решение в соответствии с имеющимися у меня ограничениями. Иногда читаемость важнее исполнения, иногда нет. Выбирайте мудро и не следуйте тенденции 2018 года по конвертации всего в рендер-реквизит.

Себастьян Лорбер
источник
1
Этот подход также упрощает передачу подпрограммы дочернему компоненту (в данном случае Hello). Начиная с React 0.14. * И далее, единственный способ передать реквизиты дочерним компонентам - это использовать React.createClone, который может быть дорогим.
Мукеш Сони
2
Вопрос: В ответе упоминается «лучшая производительность» - чего я не понимаю: лучше по сравнению с каким другим решением?
Филипп
1
HOC могут иметь лучшую производительность по сравнению с оболочками времени выполнения, поскольку они могут закорачивать рендеринг раньше.
Себастьян Лорбер
1
Спасибо! Как будто вы взяли слова из моего месяца, но выразили их с большим талантом 👍
MacKentoch
1
Это гораздо лучший ответ:] Спасибо!
cullanrocks
31

В дополнение к ответу Софи я также нашел применение при отправке дочерних типов компонентов, делая что-то вроде этого:

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});
KRS
источник
2
Я не видел никакой документации о delegateтом, как вы ее нашли?
NVI
4
Вы можете добавить любые реквизиты, которые вы хотите, к компоненту и назвать их как хотите, я использую this.props.delegate в строке 4, но с тем же успехом мог бы назвать его как-нибудь еще.
Крс