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

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

В Angular есть параметр включения.

В 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 для достижения того же результата?


person NVI    schedule 31.12.2013    source источник


Ответы (3)


Пытаться:

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

См. Несколько компонентов: дочерние элементы и Тип реквизита Children в документации для получения дополнительной информации.

person Sophie Alpert    schedule 31.12.2013
comment
Или вы можете использовать компонент более высокого порядка :) stackoverflow.com/a/31564812/82609 - person Sebastien Lorber; 22.07.2015

Использование 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. react-powerplug - это набор простых компонентов render-prop. react-accept помогает создавать композицию.


Компоненты высшего порядка (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);

Компонент высшего порядка / HOC обычно представляет собой функцию, которая принимает компонент и возвращает новый компонент.

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

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


Заключение

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

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

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

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

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

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

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

  • Композиция Context.Consumer от React
  • Unstated's Subscribe
  • с использованием graphql HOC Apollo вместо Query render prop

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

person Sebastien Lorber    schedule 22.07.2015
comment
Этот подход также упрощает передачу свойств дочернему компоненту (в данном случае Hello). Начиная с React 0.14. *, Единственный способ передать свойства дочерним компонентам - использовать React.createClone, что может быть дорогостоящим. - person Mukesh Soni; 10.09.2015
comment
Вопрос: в ответе упоминается лучшая производительность - чего я не понимаю: лучше по сравнению с каким другим решением? - person Philipp; 09.08.2016
comment
HOC могут иметь лучшую производительность по сравнению с оболочками времени выполнения, поскольку они могут прервать рендеринг раньше. - person Sebastien Lorber; 09.08.2016
comment
Когда вы используете PureComponent, вам больше не нужно возвращать функцию. Просто удалите одну функцию переноса: js 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; } - person ColorWin; 03.01.2018
comment
Спасибо! Вы как будто взяли слова из моего месяца, но выразили их более талантливо ???? - person MacKentoch; 20.09.2018
comment
Это гораздо лучший ответ:] Спасибо! - person cullanrocks; 13.09.2019
comment
Абсолютно красивый! HOC-метод был именно тем, что я искал, большое вам спасибо! - person Sam Sverko; 07.08.2020
comment
Могу ли я использовать HOC без включения PureComponent в функцию? Передать WrappedComponent в props или что-то в этом роде? - person Yechiam Weiss; 06.01.2021

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

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} />
    }
});
person krs    schedule 03.01.2014
comment
Я не видел документации по delegate, как вы ее нашли? - person NVI; 03.01.2014
comment
вы можете добавить любые реквизиты к компоненту и называть их по своему усмотрению, я использую this.props.delegate в строке 4, но с таким же успехом можно назвать его как-нибудь еще. - person krs; 03.01.2014