React.js: загортання одного компонента в інший


187

Багато мов шаблонів мають висловлювання "слоти" або "урожайність", які дозволяють зробити якусь інверсію управління, щоб обернути один шаблон всередині іншого.

У кутовому є варіант "transclude" .

Рейки мають звіт про врожайність . Якби React.js мав заяву про прибутковість, це виглядало б так:

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, щоб досягти однакового результату?


Відповіді:


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. react-powerplug - це сукупність простих рендеринг-опорних компонентів. реагувати-приймати допомагає робити композицію.


Компоненти вищого порядку (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вказівник імені не змінюється з часом, обгортка має можливість сказати: "Мені не потрібно робити рендерінг, оскільки реквізити (власне, ім'я) такі ж, як і раніше". З childrenвищенаведеним рішенням, навіть якщо обгортка є PureComponent, це не так, тому що дочірній елемент відтворюється кожного разу, коли батьківський показ відображається, а це означає, що обгортка, ймовірно, завжди буде повторно відображатися, навіть якщо загорнута складова чиста. Існує плагін Babel, який може допомогти пом’якшити це і забезпечити постійний childrenелемент у часі.


Висновок

Компоненти вищого порядку можуть забезпечити кращі показники роботи. Це не так складно, але, звичайно, спочатку виглядає недоброзичливо.

Не переносьте всю свою кодову базу до HOC після прочитання цього. Пам’ятайте лише, що на критичних шляхах вашої програми ви можете використовувати HOC замість обертових оболонок для виконання з міркувань продуктивності, особливо якщо та сама обгортка використовується багато разів, варто розглянути її як HOC.

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

Зверніть увагу, якщо компонент має API рендеринга, створити HOC поверх нього, як правило, легко, тому якщо ви є автором lib, спочатку слід написати API підтримки рендерінгу та, зрештою, запропонувати версію HOC. Це те, що робить Apollo з <Query>компонентом рендеринга і graphqlHOC, що використовує його.

Особисто я використовую і те, і інше, але коли сумніваюся, я віддаю перевагу HOCs тому, що:

  • Більш ідіоматичним є їх складання ( compose(hoc1,hoc2)(Comp)) порівняно з рендерінгами
  • Це може дати мені кращі виступи
  • Мені знайомий цей стиль програмування

Я не вагаюся використовувати / створювати HOC версії моїх улюблених інструментів:

  • Реакція Context.Consumerкомп
  • Нестандартні Subscribe
  • використовуючи graphqlHOC Apollo замість Queryнадання опори

На мою думку, іноді рендери рендерінгу роблять код більш читабельним, іноді менше ... Я намагаюся використовувати найбільш прагматичне рішення відповідно до обмежень, які у мене є. Іноді читабельність важливіша за продуктивність, іноді ні. Вибирайте розумно і не зобов’язуйтесь слідувати тенденції 2018 року перетворювати все на рендеринг.


1
Цей підхід також полегшує передачу реквізиту вниз дітям компонента (в даному випадку привіт). Від React 0.14. * Далі єдиним способом передачі реквізиту до компонентів дітей буде використання React.createClone, що може бути дорого.
Mukesh Soni

2
Питання: Відповідь зазначає "кращі показники" - що я не розумію: кращий порівняно з іншим рішенням?
Філіп

1
HOC можуть мати кращі показники порівняно з обертовими пристроями, оскільки вони можуть коротко замикати рендерінг раніше.
Себастьєн Лорбер

1
Дякую! Це як ви взяли слова з мого місяця, але ви висловлюєте їх з більшим талантом 👍
MacKentoch

1
Це набагато краща відповідь:] Дякую!
Кулларонки

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} />
    }
});

2
Я не бачив жодної документації delegate, як ви її знайшли?
NVI

4
Ви можете додати до компонента будь-який реквізит і назвати їх так, як вам захочеться, використовуючи this.props.delegate у рядку 4, але так само можна було б назвати його чимось іншим.
кр
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.