Latar Belakang

Biasanya, saat menggunakan modals dalam komponen Vue.js, adalah norma untuk membuat komponen modal yang dapat digunakan kembali, dan kemudian mengontrol status komponen ini menggunakan kejadian dari komponen turunan.

Sebagai contoh, perhatikan kode berikut:

App.vue

<div id="app">

    <!-- Main Application content here... -->

    <!-- Place any modal components here... -->
    <modal ref="ContactForm"></modal>

</div>

ChildComponent.Vue

Untuk membuka modal dari komponen anak, kita cukup memicu kejadian berikut:

bus.$emit('open-modal', 'ContactForm');

Catatan 1: bus adalah instance Vue terpisah yang memungkinkan event dipicu di antara semua komponen terlepas dari relasinya.

Catatan 2: Saya sengaja mengabaikan kode komponen modal saya karena tidak relevan dengan pertanyaan.

Masalah

Sementara hal di atas berfungsi dengan baik, ada satu masalah utama ...

Untuk menambahkan modal ke aplikasi saya, alih-alih menempatkan komponen modal di dalam komponen anak yang mereferensikannya, saya harus menempatkan semua modals dalam App.vue karena ini memastikan mereka berada setinggi mungkin di pohon DOM (untuk memastikan mereka muncul di atas semua konten).

Akibatnya, App.vue saya berpotensi terlihat seperti ini:

<div id="app">

    <!-- Main Application content here... -->

    <!-- Place any modal components here... -->
    <modal ref="SomeModal1"></modal>
    <modal ref="SomeModal2"></modal>
    <modal ref="SomeModal3"></modal>
    <modal ref="SomeModal4"></modal>
    <modal ref="SomeModal5"></modal>
    <modal ref="SomeModal6"></modal>
    <modal ref="SomeModal7"></modal>

</div>

Akan jauh lebih bersih untuk dapat menempatkan komponen modal di dalam DOM komponen anak.

Namun, untuk memastikan bahwa modal muncul di atas semua konten dalam DOM (khususnya item dengan set z-index), saya tidak dapat melihat alternatif di atas...

Adakah yang bisa menyarankan cara di mana saya dapat memastikan modals saya akan berfungsi dengan benar, bahkan jika mereka ditempatkan di dalam komponen anak?

Solusi Potensial

Saya memang memikirkan solusi berikut tetapi tampaknya sangat kotor ...

  1. Api open-modal acara
  2. Pindahkan komponen modal yang relevan ke komponen induk App.vue
  3. Tunjukkan modalnya

Informasi tambahan

Jika hal di atas tidak jelas, saya mencoba untuk menghindari pendefinisian semua modals dalam komponen App.vue saya, dan mengizinkan pendefinisian modals saya dalam komponen turunan apa pun.

Alasan saya tidak dapat melakukan ini, saat ini, adalah karena HTML untuk modals harus muncul setinggi mungkin di pohon DOM untuk memastikan mereka muncul di atas semua konten.

2
Ben Carey 16 September 2019, 23:32

2 jawaban

Jawaban Terbaik

Inilah yang saya bicarakan:

Buat fungsi addProgrammaticComponent di helper, di sepanjang baris ini:

import Vue from 'vue';

export function addProgrammaticComponent(parent, component, dataFn, extraProps = {}) {
  const ComponentClass = Vue.extend(component);
  // this can probably be simplified. 
  // It largely depends on how much flexibility you need in building your component
  // gist being: dynamically add props and data at $mount time
  const initData = dataFn ? dataFn() : {};
  const data = {};
  const propsData = {};
  const propKeys = Object.keys(ComponentClass.options.props || {});

  Object.keys(initData).forEach((key) => {
    if (propKeys.includes(key)) {
      propsData[key] = initData[key];
    } else {
      data[key] = initData[key];
    }
  });

  // add store props if you use Vuex

  // extraProps can include dynamic methods or computed, which will be merged
  // onto what has been defined in the .vue file

  const instance = new ComponentClass({
    /* store, */ data, propsData, ...extraProps,
  });

  instance.$mount(document.createElement('div'));

  // generic helper for passing data to/from parent:
  const dataSetter = (data) => {
    Object.keys(data).forEach((key) => {
        instance[key] = data[key];
    });
  };

  // set unwatch on parent as you call it after you destroy the instance
  const unwatch = parent.$watch(dataFn || {}, dataSetter);

  return {
    instance,
    update: () => dataSetter(dataFn ? dataFn() : {}),
    dispose: () => {
        unwatch();
        instance.$destroy();
    },
  };
}

... dan sekarang, di mana Anda menggunakannya:

Modal.vue adalah komponen modal yang khas, tetapi Anda dapat meningkatkannya dengan tutup dengan menekan tombol Esc atau Del, dll...

Di mana Anda ingin membuka modal:

 methods: {
   openFancyModal() {
     const component = addProgrammaticComponent(
       this,
       Modal,
       () => ({
         title: 'Some title',
         message: 'Some message',
         show: true,
         allowDismiss: true,
         /* any other props you want to pass to the programmatic component... */
       }),
     );

     document.body.appendChild(component.instance.$el);

     // here you have full access to both the programmatic component 
     // as well as the parent, so you can add logic

     component.instance.$once('close', component.dispose);

     // if you don't want to destroy the instance, just hide it
     component.instance.$on('cancel', () => {
       component.instance.show = false;
     });

     // define any number of events and listen to them: i.e:
     component.instance.$on('confirm', (args) => {
       component.instance.show = false;
       this.parentMethod(args);
     });
   },
   /* args from programmatic component */
   parentMethod(args) {
     /* you can even pass on the component itself, 
        and .dispose() when you no longer need it */
   }
 }    

Meskipun demikian, tidak ada yang menghentikan Anda untuk membuat lebih dari satu komponen Modal/Dialog/Popup, baik karena mungkin memiliki template yang berbeda atau karena mungkin memiliki fungsi tambahan yang signifikan yang akan mencemari komponen Modal generik (yaitu: LoginModal.vue, AddReportModal.vue, AddUserModal.vue, AddCommentModal.vue).

Intinya di sini adalah: mereka tidak ditambahkan ke aplikasi (ke DOM), sampai Anda benar-benar $mount mereka. Anda tidak menempatkan markup di komponen induk. Dan Anda dapat menentukan di pembukaan fn alat peraga apa yang harus dilewati, apa yang harus didengarkan, dll...

Kecuali untuk metode unwatch, yang dipicu pada induknya, semua peristiwa terikat ke instance programmaticComponent, jadi tidak ada sampah.

Inilah yang saya katakan ketika saya mengatakan tidak ada contoh modal tersembunyi yang sebenarnya bersembunyi di DOM sampai Anda membukanya.

Bahkan tidak mengatakan pendekatan ini tentu lebih baik daripada yang lain (tetapi memiliki beberapa keuntungan). Dari POV saya, itu hanya terinspirasi oleh fleksibilitas dan prinsip inti Vue, itu jelas mungkin, dan memungkinkan fleksibilitas untuk .$mount dan membuang komponen apa pun (tidak hanya modal) ke/dari komponen apa pun.

Ini sangat baik ketika Anda perlu membuka komponen yang sama dari berbagai sudut aplikasi kompleks yang sama dan Anda serius tentang KERING.

Lihat vm.$mount dokumen.

1
tao 16 September 2019, 22:43

Saya menempatkan modals saya di komponen anak saya dan itu berfungsi dengan baik. Implementasi modal saya pada dasarnya mirip dengan contoh modal dari dokumen. Saya juga menambahkan fitur a11y dasar termasuk vue-focus-lock, tetapi idenya sama.

Tidak ada bus acara, status bersama, atau referensi - cukup v-if modal menjadi ada saat dibutuhkan.

1
David Weldon 16 September 2019, 21:07