Javascript 在Vue.js的子组件中放置模态 背景

Javascript 在Vue.js的子组件中放置模态 背景,javascript,css,vue.js,modal-dialog,Javascript,Css,Vue.js,Modal Dialog,通常,在Vue.js组件中使用modals时,通常会创建可重用的modal组件,然后使用子组件中的事件控制此组件的状态 例如,考虑下面的代码: App.vue 注意1:总线是一个单独的Vue实例,它允许在所有组件之间触发事件,而不管它们之间的关系如何 注2:我故意省略了我的模态组件代码,因为它与问题无关 问题 虽然上述方法非常有效,但有一个关键问题 为了向我的应用程序添加一个modal,而不是将modal组件放在引用它的子组件中,我必须将allmodals放在App.vue中,因为这样可以确保它

通常,在Vue.js组件中使用modals时,通常会创建可重用的
modal
组件,然后使用子组件中的事件控制此组件的状态

例如,考虑下面的代码:

App.vue
注意1:
总线是一个单独的Vue实例,它允许在所有组件之间触发事件,而不管它们之间的关系如何

注2:我故意省略了我的
模态
组件代码,因为它与问题无关

问题 虽然上述方法非常有效,但有一个关键问题

为了向我的应用程序添加一个
modal
,而不是将
modal
组件放在引用它的子组件中,我必须将allmodals放在
App.vue
中,因为这样可以确保它们在DOM树中尽可能高(以确保它们出现在所有内容之上)

因此,my
App.vue
可能最终会变成这样:

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

如果能够将
modal
组件放在子组件的DOM中,会更干净

但是,为了确保模式出现在DOM中的所有内容之上(特别是带有set
z-index
的项目),我看不到上述模式的替代方案

有谁能建议一种方法,我可以确保我的模态正确工作,即使它们被放置在子组件中

势解 我确实考虑过下面的解决方案,但它看起来很脏

  • 火灾
    打开模式
    事件
  • 将相关的
    modal
    组件移动到父
    App.vue
    组件
  • 显示模态
  • 补充资料 如果上述内容不清楚,我会尽量避免在我的
    App.vue
    组件中定义all模态,并允许在任何子组件中定义我的模态


    目前我无法做到这一点的原因是,模态的HTML必须在DOM树中显示得尽可能高,以确保它们出现在所有内容之上。

    我将模态放在子组件中,效果非常好。我的模式实现与文档中的基本相似。我还添加了一些基本的a11y功能,包括,但想法是一样的


    没有事件总线、共享状态或引用-只要
    v-if
    模型在需要时就存在。

    以下是我所说的:

    在助手中创建一个
    addProgrammaticComponent
    函数,如下所示:

    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();
        },
      };
    }
    
    。。。现在,在您使用它的地方:

    Modal.vue
    是一个典型的模式,但您可以通过关闭Esc或Del keyspress等来增强它

    要在其中打开模式文件:

     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 */
       }
     }    
    
    也就是说,没有人会阻止您创建多个模态/对话框/弹出组件,因为它可能有不同的模板,或者因为它可能有大量额外的功能,这会污染通用的
    模态
    组件(即:
    LoginModal.vue
    AddReportModal.vue
    AddUserModal.vue
    AddCommentModal.vue

    这里的要点是:它们不会添加到应用程序(到DOM),直到您实际安装它们。您不会将标记放置在父组件中。您可以在开头定义传递什么道具,听什么,等等

    除了父级触发的
    unwatch
    方法外,所有事件都绑定到programmaticComponent实例,因此没有垃圾

    这就是我所说的,当我说在打开DOM之前,DOM上没有隐藏的模态实例

    甚至没有说这种方法一定比其他方法更好(但它有一些优点)。从我的观点来看,它只是受Vue的灵活性和核心原则的启发,这显然是可能的,它允许灵活地
    $mount
    和在任何组件上/从任何组件上处置任何组件(不仅仅是modal)

    当你需要从同一个复杂应用程序的多个角落打开同一个组件,并且认真对待DRY时,这一点尤其好


    参见文档。

    规范,正如您所说的,是重用。如中所述,您只有一个模态实例,并且在打开它时动态注入内容。事实上,今天的标准是没有任何模态实例,而是在需要时以编程方式创建一个模态实例,并在将回调响应传递给其触发组件后销毁它。如果您最终得到一堆模态实例,你没有重用。你是在硬编码。@AndreiGheorghiu-我的措辞很糟糕,你解释它的方式正是我的模态组件的工作方式。因为在每个
    模态中
    都是有条件地呈现的,所以它们不会同时出现在DOM中。在这种情况下,你只需要将
    设置为目标元素当你附加它时,它是模态实例的一部分。这就是避免任何
    z-index
    问题的方法。@AndreiGheorghiu-等等,我有点困惑。你说的
    target
    元素是什么意思?
    modal
    不是由我的JS创建的,它是由Vue有条件地呈现的。JS你有一个
    .modal{z-index:2000;}的用例吗
    不起作用?对于现代浏览器,z-index的理论最大值大约为20亿。因此,我不再担心它在DOM中的位置,而是在有意义的地方添加组件。感谢您的回答,但是,这并不能解决我的问题。我的
    模式
    实现与Vue.js示例非常相似。我能够要将模态定义放在子组件中,从技术上讲,它们是有效的。问题是,通过将它们放在子组件中,Vue会将它们呈现在DOM中的该位置,并且
    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();
        },
      };
    }
    
     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 */
       }
     }