Vue: Diskusi: Cara terbaik untuk membuat HOC

Dibuat pada 25 Jul 2017  ·  40Komentar  ·  Sumber: vuejs/vue

Versi: kapan

2.4.2

Tautan reproduksi

https://jsfiddle.net/o7yvL2jd/

Langkah-langkah untuk mereproduksi

Saya telah mencari cara yang tepat untuk mengimplementasikan HoC dengan vue.js. Tetapi tidak dapat menemukan contoh yang cocok.
Tautan di bawah ini adalah implementasi HoC yang diketahui. Tapi tidak bekerja diharapkan.
https://jsfiddle.net/o7yvL2jd/

Bagaimana saya bisa mengimplementasikan HoC dengan vue.js?
Saya hanya ingin tahu bagaimana menerapkan HoC dengan cara react.js.

Apa yang diharapkan?

Mereka adalah HoC yang hanya membuat komponen yang diteruskan sebagai parameter.
HoC yang berisi slot dan event akan dirender secara normal.

Apa yang sebenarnya terjadi?

Elemen yang akan dirender tidak ada, atau urutan yang dirender berbeda dari komponen dasar.
Beberapa implementasi HoC tidak bekerja dengan event handler.

discussion

Komentar yang paling membantu

Oke, jadi saya bermain-main dengan cara untuk membuatnya lebih mudah.

Lihat di sini: https://jsfiddle.net/Linusborg/j3wyz4d6/

Saya tidak senang dengan API karena ini adalah sketsa ide yang sangat kasar, tetapi dapat melakukan apa pun yang seharusnya dapat dilakukan.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC akan menangani:

  • menyalin alat peraga dari komponen
  • menukar $createElement dengan itu dari induknya untuk penyelesaian slot yang tepat.
  • tambahkan nama
  • jika tidak ada argumen kedua yang diberikan (seperti dalam contoh di atas), itu membuat komponen dan meneruskan:

    • Atribut

    • attrs

    • pendengar

    • slot biasa

    • slot terbatas

Itu sendiri tidak terlalu berguna, tentu saja. Jadi kesenangan terjadi pada argumen kedua yang merupakan objek Komponen sederhana.

Jika Anda ingin menulis fungsi render sendiri, tentu saja bisa. Jika Anda hanya ingin memperluas props, attrs, atau listener, Anda dapat menggunakan helper createRenderFn . itu akan membuat fungsi render seperti default yang dijelaskan di atas tetapi akan menggabungkan attrs , props atau listeners yang Anda teruskan dengan yang dari induknya.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Jika Anda ingin menulis fungsi render Anda sendiri, Anda dapat menggunakan helper normalizeSlots untuk mengubah objek dari this.$slots menjadi array yang tepat untuk diteruskan:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Komentar diinginkan :)

Semua 40 komentar

Halo @eu81273

Terima kasih atas minat Anda pada proyek ini.

Namun, masalah Anda adalah pertanyaan penggunaan/dukungan, dan pelacak masalah disediakan khusus untuk laporan bug dan permintaan fitur (seperti yang diuraikan dalam Panduan Berkontribusi kami).

Kami mendorong Anda untuk menanyakannya di forum , Stack Overflow atau di obrolan perselisihan kami dan dengan senang hati membantu Anda di luar sana.

FWIW, saya melihat keluar dari kepentingan pribadi - ini harus bekerja 100% dari kasus.

const HOC = WrappedComponent => ({
  props: typeof WrappedComponent === 'function' // accept both a construtor and an options object
    ? WrappedComponent.options.props 
    : WrappedComponent.props,
  render (h) {
    // reduce all slots to a single array again.
    const slots = Object.keys(this.$slots).reduce((arr, key) => arr.concat(this.$slots[key]), []);
    return h(WrappedComponent, {
      attrs: this.$attrs,
      props: this.$props,
      on: this.$listeners,
    }, slots);
 }
});

Saya mengedit HOC04 dalam contoh Anda karena itu yang paling dekat dengan solusi:

https://jsfiddle.net/Linusborg/o7yvL2jd/22/

Sunting: masih ada masalah dengan slot, selidiki ...

Saya mungkin telah menyelesaikannya: https://jsfiddle.net/BogdanL/ucpz8ph4/ . Slot baru saja di-hardcode sekarang, tapi itu sepele untuk dipecahkan.

Tampaknya solusinya ada di sepanjang metode @lbogdan tetapi createElement harus memiliki cara mengambil slot, seperti halnya dapat mengambil scopedSlots.

Namun, _masih_ banyak usaha untuk membuat HoC. Ada banyak hal yang harus diingat untuk dilalui, sementara dengan reaksi Anda hanya membuat WrappedComponent dengan props.

Saya baru saja memikirkan solusi yang sangat sederhana ... beri tahu saya jika saya melewatkan sesuatu di sini:

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Berdasarkan contoh yang diberikan oleh @LinusBorg dan @lbogdan , implementasi HoC paling minimal yang dapat menangani komponen dengan slot adalah:

const HoC = WrappedComponent => ({
    props: typeof WrappedComponent === 'function' 
        ? WrappedComponent.options.props 
        : WrappedComponent.props,
    render (h) {
        const slots = this.$slots;
        const scopedSlots = {};
        Object.keys(slots).map(key => (scopedSlots[key] = () => slots[key]));

        return h(WrappedComponent, {
            attrs: this.$attrs,
            props: this.$props,
            on: this.$listeners,
            scopedSlots,
        });
     }
});

Seperti yang disebutkan @blocka , masih banyak upaya untuk membuat HoC dengan vue.js.

@eu81273

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Ini tampaknya lulus ujian Anda, bukan? Tentu saja, Anda harus menyesuaikannya tergantung pada apakah WrappedComponent adalah konstruktor atau objek, tetapi tidak perlu melewatkan slot, acara, atau properti.

masih banyak upaya untuk membuat HoC dengan vue.js.

Terlepas dari masalah slot, ini hanya karena fakta bahwa Vue memang memiliki API yang lebih kompleks daripada React, yang dalam skenario ini merupakan kerugian. Saya mengagumi API minimal Reacts dalam kasus penggunaan semacam ini - Vue baru saja dirancang dengan tujuan yang sedikit berbeda, jadi HOC tidak datang semudah di React.

Tetapi seharusnya cukup sepele untuk membuat fungsi pembantu createHOC() yang membungkus pengaturan awal ini untuk Anda, bukan?

Yah, itu benar-benar tergantung apa tujuan akhirnya. Dari apa yang saya pahami, tujuan HoC adalah entah bagaimana mengubah (menghias) komponen asli (WrappedComponent) untuk menambahkan (atau menyuntikkan) alat peraga, metode, pendengar acara, dll. (seperti mixin, sungguh :smile: ). HOC06 varian hanya mengubah definisi komponen, tidak mengubah cara pembuatannya.

@blocka Tujuan dari HOC sering kali adalah untuk mendapatkan status (misalnya dari redux / vuex) dan menyuntikkannya ke props komponen yang dibungkus - yang tidak akan bekerja dengan pendekatan Anda.

@LinusBorg benar. Saya tahu itu terlalu bagus untuk menjadi kenyataan dan bahwa saya melupakan sesuatu yang jelas.

Saya pikir ini contoh yang baik untuk menerapkan kasus penggunaan nyata HoC di Vue: https://github.com/ktsn/vuex-connect.

Vue Hocs akan menjadi nilai tambah yang luar biasa (karena hampir selalu muncul dalam debat vue vs react). Mungkin repo resmi dapat dibuat untuk mengembangkan paket pembuat vue-hoc? Dengan begitu kami dapat mengerjakan implementasi yang kuat, didukung,

Btw, dapatkan cara yang lebih baik: gunakan $createElement dari komponen induk alih-alih milik HOC - ini membuat anak menyelesaikan slot dengan benar:

https://jsfiddle.net/o7yvL2jd/23/

Lucu, tetapi ada lebih banyak alasan untuk ada beberapa alat resmi jadi kami
tidak semua terus menemukan kembali kode ini.

Pada hari Minggu, 30 Juli 2017 pukul 16.33, Thorsten [email protected]
menulis:

Btw, dapatkan cara yang lebih baik: gunakan $createElement dari komponen induk
alih-alih milik HOC - ini membuat anak menyelesaikan slot dengan benar:

https://jsfiddle.net/o7yvL2jd/23/


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

Saya minta maaf karena belum memberikan solusi resmi. Senang Anda pikir itu lucu sekalipun.

Solusi saya juga tidak sempurna, ada masalah lain yang harus diselesaikan - yaitu slot yang dicakup tidak akan berfungsi dengan trik terbaru saya.

edit: oh, tidak apa-apa, mereka bekerja

Solusi resmi mungkin akan dilakukan, setidaknya saya berharap begitu - tetapi harus dipikirkan dan diuji dengan cermat.

Oke, jadi saya bermain-main dengan cara untuk membuatnya lebih mudah.

Lihat di sini: https://jsfiddle.net/Linusborg/j3wyz4d6/

Saya tidak senang dengan API karena ini adalah sketsa ide yang sangat kasar, tetapi dapat melakukan apa pun yang seharusnya dapat dilakukan.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC akan menangani:

  • menyalin alat peraga dari komponen
  • menukar $createElement dengan itu dari induknya untuk penyelesaian slot yang tepat.
  • tambahkan nama
  • jika tidak ada argumen kedua yang diberikan (seperti dalam contoh di atas), itu membuat komponen dan meneruskan:

    • Atribut

    • attrs

    • pendengar

    • slot biasa

    • slot terbatas

Itu sendiri tidak terlalu berguna, tentu saja. Jadi kesenangan terjadi pada argumen kedua yang merupakan objek Komponen sederhana.

Jika Anda ingin menulis fungsi render sendiri, tentu saja bisa. Jika Anda hanya ingin memperluas props, attrs, atau listener, Anda dapat menggunakan helper createRenderFn . itu akan membuat fungsi render seperti default yang dijelaskan di atas tetapi akan menggabungkan attrs , props atau listeners yang Anda teruskan dengan yang dari induknya.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Jika Anda ingin menulis fungsi render Anda sendiri, Anda dapat menggunakan helper normalizeSlots untuk mengubah objek dari this.$slots menjadi array yang tepat untuk diteruskan:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Komentar diinginkan :)

@LinusBorg Sangat bagus!

Apa yang saya pikir akan membantu adalah membuat kasus penggunaan HoC kehidupan nyata dan menyelesaikannya menggunakan primitif ini.

Sangat.

Saya menyebutkan masalah ini (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Karena Anda menggunakan $createElement API yang tidak berdokumen, Sebaiknya dokumentasikan untuk pengembang plugin.

Tautan Anda salah (kecuali jika Anda benar-benar ingin menautkan ke masalah dari 2014)

Tapi ya, secara teknis $ceateElement API masih hilang di halaman API.

@AlexandreBonaventure Masalah itu dari vue 0.x hari. :senyum:
Juga, createElement didokumentasikan di sini: https://vuejs.org/v2/guide/render-function.html#createElement -Arguments .

Fungsi ini didokumentasikan sebagai argumen dari fungsi render, tetapi tidak tersedia melalui this.$createElement . Ada masalah terbuka untuk itu di vuejs/vuejs.org/issues/658

@LinusBorg Tapi pada dasarnya fungsi yang sama yang dikirim ke fungsi render() , kan?

persis sama. Hanya saja tidak didokumentasikan dengan jelas bahwa itu juga tersedia di luar fungsi render melalui metode instance ini.

Saya hanya bermain-main dengan contoh di atas dan ada beberapa masalah saat menggunakannya dalam skenario yang lebih kompleks, oleh karena itu perlunya repo resmi. Beberapa catatan:

  • createRenderFn harus memeriksa apakah attrs/props/listeners adalah fungsi dan mengevaluasinya, ini akan memungkinkan Anda untuk mengatur props dll secara dinamis berdasarkan props yang ada.
  • untuk komposisi komponen harus menjadi parameter kedua, dan jika seluruh metode createHOC di-kari, kita dapat dengan mudah menghubungkan beberapa pembuat hoc bersama-sama.
  • karena hoc ditambahkan sebagai mixin, jika Anda mencoba untuk menggabungkan 2 hoc (yaitu withDefaultProps(withProps(component, {}), {}) hoc kedua tidak memiliki akses ke contoh props induk

Hai teman-teman, saya telah melihat penulisan implementasi ini.
https://github.com/jackmellis/vue-hoc/tree/develop
Beri tahu saya pendapat Anda dan saya akan segera menerbitkannya. Saya juga berpikir untuk menulis paket gaya recompose.

Paket gaya komposisi ulang akan sangat bagus. Benar-benar bisa menggunakan sesuatu
seperti withState baru-baru ini.

Pada Sabtu, 19 Agustus 2017 pukul 05:50, [email protected] menulis:

Hai teman-teman, saya telah melihat penulisan implementasi ini.
https://github.com/jackmellis/vue-hoc/tree/develop
Beri tahu saya pendapat Anda dan saya akan segera menerbitkannya. Saya juga
berpikir tentang menulis paket recompose-style.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis Bagus bahwa Anda memimpin ini :)

Beberapa pemikiran tentang umpan balik yang Anda berikan sebelumnya:

  • Saya pikir versi kari adalah poin yang bagus dan harus menjadi "default", karena begitulah cara HOC umumnya dilakukan, bukan?
  • Poin bagus tentang masalah dengan mixin. Apakah Anda sudah memiliki ide bagaimana mengurangi ini? Saya tidak memilikinya saat ini, tetapi firasat saya adalah bahwa ini harus dapat dikurangi dalam kombinasi varian kari yang Anda buat dan menggunakan Vue.config.optionsMergeStrategies

Saya juga berpikir tentang penamaan. Saya tidak suka createRenderFn , sesuatu seperti renderComponentWith akan lebih bermakna dan lebih masuk akal dalam skenario di mana kami menyematkannya di beberapa node lain:

render(h) {
  return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
}

  • Akhirnya saya memilih keduanya. Jadi createHOC adalah non-kari komponen pertama, dan kemudian ada varian kari createHOCc . Ekosistem React sangat berfokus pada fungsi, tidak seperti Vue, yang bertindak lebih seperti kerangka kerja OOP. Jadi saya pikir yang terbaik adalah tetap konsisten dengan Vue lainnya, tetapi tetap menawarkan alternatif fungsional.
  • Saya baru saja menambahkan beberapa kode untuk menangani ini. Daripada memiliki seluruh hoc sebagai mixin, saya menggabungkan hoc dan opsi secara manual menggunakan opsiMergeStrategies. Satu-satunya masalah dengan itu adalah optionsMergeStrategies mengharapkan vm sebagai parameter terakhir, tapi saya melakukan penggabungan sebelum instantiasi, jadi tidak ada vm.
  • Saya cukup senang dengan createRenderFn karena itulah yang dilakukannya. Tetapi semakin saya menggabungkan ini, semakin sedikit saya pikir orang akan menggunakannya secara langsung. Penggunaan umum akan menjadi seperti:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

Mungkin saya tidak begitu mengerti contoh Anda?

Mungkin saya tidak begitu mengerti contoh Anda?

Tidak, saya pikir Anda berada di jalur yang benar, pikiran saya menuju ke arah yang sama ketika saya memikirkannya lagi setelah balasan terakhir saya.

Dalam POC awal saya, saya memasukkannya sehingga orang dapat menambahkan markup tambahan di sekitar komponen yang dirender, tetapi itu berarti itu bukan HOC lagi, karena akan membawa UI juga ...

Ya, saya pikir Anda mencoba membuat konten tambahan, Anda telah keluar dari wilayah HOC dan mungkin juga membuat SFC untuk menanganinya.

Saya baru saja menerbitkan vue-hoc di npm!

Saya juga sedang mengerjakan vue-compose yang merupakan sesi dokumentasi cepat dari kesiapan juga. Meskipun mirip dengan recompose, Vue menangani banyak hal pintar (seperti komputasi caching dan mendorong penggunaan this ) sehingga sebenarnya tidak memerlukan sintaks yang cukup rumit (atau gaya fungsional).

extends bekerja secara berbeda, tetapi dapat digunakan untuk mencapai hasil yang serupa, itu benar.

Saya akan menutup ini karena saya pikir proyek @jackmellis memang memberikan dasar yang kuat. Kami tidak bermaksud memasukkan cara untuk membuat HOC menjadi inti, terutama karena kami tidak melihat manfaat besar dari mixin / Extends.

terutama karena kami tidak melihat manfaat besar dari mixin / Extends.

@LinusBorg React telah meninggalkan mixin , HOC membawa banyak manfaat.

Artikel ini telah menganalisis manfaatnya:

  • Kontrak antara komponen dan mixinnya bersifat implisit.
  • Saat Anda menggunakan lebih banyak mixin dalam satu komponen, mereka mulai berbenturan.
  • Mixin cenderung menambahkan lebih banyak status ke komponen Anda sedangkan Anda harus berusaha lebih sedikit.
  • ....

Saya pikir tim Vue harus mempertimbangkan untuk mendukung HoC lebih hati-hati....walaupun tidak mudah (Sepertinya Vue tidak dirancang dengan cara ini).

Saya tidak yakin bahwa HoC adalah konsep yang superior. Potensi bentrokan "kontrak implisit" juga dapat terjadi dengan HoC, misalnya.

Lihat pembicaraan ini Oleh pengelola React-router tentangnya: https://www.youtube.com/watch?v=BcVAq3YFiuc

Meskipun demikian, menurut saya mereka juga tidak buruk , mereka berguna dalam banyak situasi. Mereka bukan peluru ajaib yang mereka puji seperti di dunia React, meskipun, mereka memiliki kelemahannya sendiri.

Seperti yang terlihat dari diskusi di atas, mengimplementasikan HoC di Vue tidak sepele seperti di React karena API dan fungsionalitas Vue lebih luas dan lebih banyak kasus tepi yang harus ditangani.

Kita pasti dapat berbicara tentang bagaimana meningkatkan ini selama tidak perlu merusak apa pun di Vue - HoC tidak layak untuk perubahan besar dalam pandangan saya.

666

implementasi HoC paling minimal yang dapat menangani komponen dengan slot adalah:

function hoc(WrappedComponent) {
  return {
    render(h) {
      return h(WrappedComponent, {
        on: this.$listeners,
        attrs:this.$attrs,
        scopedSlots: this.$scopedSlots,
      });
    },
  };
}

membandingkan dengan jawaban di atas,

  • props config tidak perlu, yang dapat diteruskan dari this.$attrs ke anak
  • slot tambahan tidak diperlukan, this.$scopedSlots berisi slot sejak v2.6.0+

saya menulis contoh untuk memeriksa

Apakah halaman ini membantu?
0 / 5 - 0 peringkat