React.js: Membungkus satu komponen ke komponen lain

187

Banyak bahasa templat memiliki pernyataan "slot" atau "menghasilkan", yang memungkinkan untuk melakukan semacam inversi kontrol untuk membungkus satu templat di dalam templat yang lain.

Angular memiliki opsi "transclude" .

Rails memiliki pernyataan hasil . Jika React.js memiliki pernyataan hasil, akan terlihat seperti ini:

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

Output yang diinginkan:

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

Sayangnya, React.js tidak memiliki <yield/>. Bagaimana cara menetapkan komponen Wrapper untuk mencapai hasil yang sama?

NVI
sumber

Jawaban:

159

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

Ini juga dikenal sebagai transclusiondi Angular.

childrenadalah prop khusus di Bereaksi dan akan berisi apa yang ada di dalam tag komponen Anda (di sini <App name={name}/>ada di dalam Wrapper, jadi itu adalahchildren

Perhatikan bahwa Anda tidak perlu menggunakan children, yang unik untuk komponen, dan Anda juga dapat menggunakan alat peraga normal jika Anda mau, atau mencampur alat peraga dan anak-anak:

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

Ini sederhana dan bagus untuk banyak usecases, dan saya akan merekomendasikan ini untuk sebagian besar aplikasi konsumen.


render alat peraga

Dimungkinkan untuk meneruskan fungsi render ke komponen, pola ini umumnya disebut render prop, danchildren penyangga sering digunakan untuk menyediakan panggilan balik itu.

Pola ini tidak dimaksudkan untuk tata letak. Komponen pembungkus umumnya digunakan untuk menahan dan mengelola beberapa keadaan dan menyuntikkannya dalam fungsi render-nya.

Contoh penghitung:

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

Anda bisa menjadi lebih mewah dan bahkan menyediakan objek

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

Perhatikan bahwa Anda tidak perlu menggunakannya children, ini adalah masalah selera / API.

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

Sampai hari ini, banyak perpustakaan menggunakan render props (React context, React-motion, Apollo ...) karena orang cenderung menemukan API ini lebih mudah daripada HOC. react-powerplug adalah kumpulan komponen render-prop sederhana. Reaksi-adopsi membantu Anda melakukan komposisi.


Komponen Tingkat Tinggi (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);

Sebuah Tingkat Tinggi Komponen / HOC umumnya merupakan fungsi yang mengambil komponen dan mengembalikan komponen baru.

Menggunakan Komponen Orde Tinggi dapat lebih berkinerja daripada menggunakan childrenatau render props, karena pembungkus dapat memiliki kemampuan untuk hubungan pendek rendering selangkah lebih maju dengan shouldComponentUpdate.

Di sini kita gunakan PureComponent. Saat merender ulang aplikasi, jika WrappedAppprop nama tidak berubah dari waktu ke waktu, pembungkusnya memiliki kemampuan untuk mengatakan "Saya tidak perlu merender karena props (sebenarnya, nama) sama seperti sebelumnya". Dengan childrensolusi berbasis di atas, bahkan jika pembungkusnya PureComponent, itu tidak terjadi karena elemen anak-anak diciptakan kembali setiap kali orangtua membuat, yang berarti pembungkus kemungkinan akan selalu merender ulang, bahkan jika komponen yang dibungkus adalah murni. Ada plugin babel yang dapat membantu mengurangi ini dan memastikan childrenelemen konstan dari waktu ke waktu.


Kesimpulan

Komponen Tingkat Tinggi dapat memberi Anda kinerja yang lebih baik. Ini tidak terlalu rumit tetapi pada awalnya terlihat tidak ramah.

Jangan bermigrasi seluruh basis kode Anda ke HOC setelah membaca ini. Ingatlah bahwa pada jalur kritis aplikasi Anda, Anda mungkin ingin menggunakan HOC alih-alih pembungkus runtime karena alasan kinerja, terutama jika pembungkus yang sama digunakan berkali-kali, ada baiknya mempertimbangkan menjadikannya HOC.

Redux pada awalnya menggunakan pembungkus runtime <Connect>dan kemudian beralih ke HOC connect(options)(Comp)karena alasan kinerja (secara default, pembungkus itu murni dan digunakan shouldComponentUpdate). Ini adalah ilustrasi sempurna dari apa yang ingin saya soroti dalam jawaban ini.

Catatan jika suatu komponen memiliki API render-prop, umumnya mudah untuk membuat HOC di atasnya, jadi jika Anda seorang penulis lib, Anda harus menulis API render prop terlebih dahulu, dan akhirnya menawarkan versi HOC. Inilah yang Apollo lakukan dengan <Query>komponen render-prop, dan graphqlHOC menggunakannya.

Secara pribadi, saya menggunakan keduanya, tetapi ketika ragu saya lebih suka HOC karena:

  • Lebih idiomatik untuk membuat mereka ( compose(hoc1,hoc2)(Comp)) dibandingkan dengan membuat alat peraga
  • Itu bisa memberi saya penampilan yang lebih baik
  • Saya akrab dengan gaya pemrograman ini

Saya tidak ragu menggunakan / membuat versi HOC dari alat favorit saya:

  • React's Context.Consumercomp
  • Tidak dinyatakan Subscribe
  • menggunakan graphqlHOC dari Apollo sebagai ganti Queryrender prop

Menurut pendapat saya, kadang-kadang membuat alat peraga membuat kode lebih mudah dibaca, kadang-kadang kurang ... Saya mencoba menggunakan solusi yang paling pragmatis sesuai dengan kendala yang saya miliki. Terkadang keterbacaan lebih penting daripada penampilan, kadang tidak. Pilih dengan bijak dan jangan ikuti tren 2018 untuk mengubah segalanya menjadi render-props.

Sebastien Lorber
sumber
1
Pendekatan ini juga membuatnya mudah untuk melewatkan alat peraga komponen do child (dalam hal ini Halo). Dari React 0.14. * Dan seterusnya, satu-satunya cara untuk meneruskan alat peraga ke komponen anak-anak adalah dengan menggunakan React.createClone, yang mungkin mahal.
Mukesh Soni
2
Pertanyaan: Jawabannya menyebutkan "kinerja yang lebih baik" - apa yang saya tidak mengerti: lebih baik dibandingkan dengan solusi lain mana?
Philipp
1
HOCs dapat memiliki kinerja yang lebih baik dibandingkan dengan pembungkus runtime, karena mereka dapat membuat hubungan pendek rendering sebelumnya.
Sebastien Lorber
1
Terima kasih! Ini seperti Anda mengambil kata-kata dari bulan saya tetapi Anda mengekspresikannya dengan talenta yang lebih besar đź‘Ť
MacKentoch
1
Ini adalah jawaban yang jauh lebih baik:] Terima kasih!
cullanrocks
31

Selain jawaban Sophie, saya juga menemukan kegunaan dalam mengirimkan tipe komponen anak, melakukan sesuatu seperti ini:

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
sumber
2
Saya belum melihat dokumentasi apa pun delegate, bagaimana Anda menemukannya?
NVI
4
Anda dapat menambahkan alat peraga apa pun yang Anda inginkan ke suatu komponen dan menamainya seperti yang Anda inginkan, saya menggunakan this.props.delegate di baris 4 tetapi bisa saja menamakannya sesuatu yang lain.
krs