React.js: Zawijanie jednego komponentu w inny

187

Wiele języków szablonów ma instrukcje „sloty” lub „fed”, które umożliwiają wykonanie pewnego rodzaju inwersji kontroli w celu zawinięcia jednego szablonu w drugi.

Angular ma opcję „przetłumacz” .

Railsy mają deklarację dochodu . Gdyby React.js miał instrukcję fed, wyglądałoby to tak:

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

Pożądane wyjście:

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

Niestety, React.js nie ma <yield/>. Jak zdefiniować komponent Wrapper, aby uzyskać ten sam wynik?

NVI
źródło

Odpowiedzi:

159

Za pomocą 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);

Jest to również znane jako transclusionAngular.

childrenjest specjalnym rekwizytem w React i będzie zawierał to, co jest w tagach twojego komponentu (tutaj <App name={name}/>jest wewnątrz Wrapper, więc to jestchildren

Pamiętaj, że niekoniecznie musisz używać childrenunikalnego komponentu i możesz także użyć normalnych rekwizytów, jeśli chcesz, lub mieszać rekwizyty i dzieci:

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

Jest to proste i odpowiednie dla wielu przypadków użycia, i polecam to dla większości aplikacji konsumenckich.


renderować rekwizyty

Możliwe jest przekazanie funkcji renderowania do komponentu, ten wzorzec jest ogólnie nazywany render prop, achildren rekwizyt jest często używany do zapewnienia tego wywołania zwrotnego.

Ten wzór nie jest tak naprawdę przeznaczony do układu. Komponent opakowania jest zwykle używany do przechowywania i zarządzania niektórymi stanami oraz wprowadzania ich w funkcje renderowania.

Przykład licznika:

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

Możesz uzyskać jeszcze więcej fantazji, a nawet zapewnić obiekt

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

Pamiętaj, że niekoniecznie musisz go używać children, jest to kwestia gustu / API.

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

Na dzień dzisiejszy wiele bibliotek używa rekwizytów renderujących (kontekst React, React-motion, Apollo ...), ponieważ ludzie uważają, że ten interfejs API jest łatwiejszy niż HOC. React-powerplug to zbiór prostych komponentów render-prop. Reaguj adoptuj pomaga tworzyć kompozycje.


Komponenty wyższego rzędu (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);

Składnik wyższego rzędu / HOC jest na ogół funkcją, która pobiera komponent i zwraca nowy komponent.

Użycie komponentu wyższego rzędu może być bardziej wydajne niż użycie childrenlub render props, ponieważ opakowanie może mieć możliwość zwarcia renderowania o krok do przodu shouldComponentUpdate.

Tutaj korzystamy PureComponent. Podczas ponownego renderowania aplikacji, jeśli WrappedAppnazwa rekwizytu nie zmienia się w czasie, opakowanie może powiedzieć „Nie muszę renderować, ponieważ rekwizyty (właściwie nazwa) są takie same jak poprzednio”. W przypadku childrenpowyższego rozwiązania bazowego, nawet jeśli opakowanie jest PureComponent, nie jest tak, ponieważ element potomny jest odtwarzany za każdym razem, gdy moduł nadrzędny renderuje, co oznacza, że ​​opakowanie prawdopodobnie zawsze będzie ponownie renderowane, nawet jeśli opakowany komponent jest czysty. Istnieje wtyczka babel, która może to złagodzić i zapewnić stały childrenelement z czasem.


Wniosek

Komponenty wyższego rzędu mogą zapewnić lepszą wydajność. Nie jest to takie skomplikowane, ale z początku wygląda nieprzyjaźnie.

Nie migruj całej bazy kodu do HOC po przeczytaniu tego. Pamiętaj tylko, że ze względów wydajnościowych na krytycznych ścieżkach aplikacji możesz używać HOC zamiast owijek wykonawczych, szczególnie jeśli często używasz tego samego opakowania, warto rozważyć utworzenie HOC.

Redux używał początkowo opakowania środowiska wykonawczego, <Connect>a później przełączył się na HOC connect(options)(Comp)ze względu na wydajność (domyślnie opakowanie jest czyste i używa shouldComponentUpdate). To idealna ilustracja tego, co chciałem podkreślić w tej odpowiedzi.

Uwaga: jeśli komponent ma API renderowania-prop, na ogół łatwo jest utworzyć na nim HOC, więc jeśli jesteś autorem lib, powinieneś najpierw napisać API renderowania-prop, a ostatecznie zaoferować wersję HOC. To właśnie robi Apollo z <Query>komponentem render-prop i graphqlużywającym go HOC.

Osobiście używam obu, ale w razie wątpliwości wolę HOC, ponieważ:

  • Bardziej idiomatyczne jest komponowanie ich ( compose(hoc1,hoc2)(Comp)) w porównaniu do renderowania rekwizytów
  • Może dać mi lepsze wyniki
  • Znam ten styl programowania

Nie waham się używać / tworzyć wersji HOC moich ulubionych narzędzi:

  • React's Context.Consumercomp
  • Unstated's Subscribe
  • używając graphqlHOC Apollo zamiast Queryrenderowania prop

Moim zdaniem, czasami renderowanie rekwizytów sprawia, że ​​kod jest bardziej czytelny, a czasem mniej ... Staram się stosować najbardziej pragmatyczne rozwiązanie zgodnie z moimi ograniczeniami. Czasami czytelność jest ważniejsza niż występy, a czasem nie. Wybieraj mądrze i nie podążaj za trendem w 2018 r. Polegającym na przekształcaniu wszystkiego w render-rekwizyty.

Sebastien Lorber
źródło
1
Takie podejście ułatwia także przekazywanie rekwizytów do elementu potomnego (w tym przypadku Witaj). Począwszy od React 0.14. *, Jedynym sposobem przekazywania rekwizytów komponentom potomnym byłoby użycie React.createClone, co może być kosztowne.
Mukesh Soni,
2
Pytanie: Odpowiedź wspomina o „lepszej wydajności” - czego nie rozumiem: lepiej w porównaniu z jakim innym rozwiązaniem?
Philipp
1
HOC mogą mieć lepszą wydajność w porównaniu do owijek wykonawczych, ponieważ mogą wcześniej spowodować zwarcie renderowania.
Sebastien Lorber,
1
Dziękuję Ci! To tak, jakbyś wziął słowa z mojego miesiąca, ale wyrażasz je z większym talentem 👍
MacKentoch
1
To o wiele lepsza odpowiedź:] Dzięki!
cullanrocks
31

Oprócz odpowiedzi Sophie znalazłem również zastosowanie w wysyłaniu typów komponentów potomnych, robiąc coś takiego:

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
źródło
2
Nie widziałem żadnej dokumentacji delegate, jak to znalazłeś?
NVI
4
możesz dodać dowolne rekwizyty do komponentu i nazwać je tak, jak chcesz, używając im this.props.delegate w wierszu 4, ale równie dobrze mógł nazwać to czymś innym.
krs