背景
通常,在 Vue.js 组件中使用模态时,通常创建一个可重用的 modal
组件,然后使用子组件中的事件控制该组件的状态。
例如,考虑以下代码:
App.vue
<div id="app">
<!-- Main Application content here... -->
<!-- Place any modal components here... -->
<modal ref="ContactForm"></modal>
</div>
ChildComponent.Vue
要从子组件打开模式,我们只需触发以下事件:
bus.$emit('open-modal', 'ContactForm');
注释 1: bus
是一个单独的 Vue 实例,它允许在所有组件之间触发事件,无论它们之间的关系如何。
注2:我故意省略了我的modal
组件代码,因为它与问题无关。
问题
虽然上述工作绝对正常,但有一个关键问题......
为了将modal
添加到我的应用程序中,我必须将all<,而不是将modal
组件放置在引用它的子组件中/strong> App.vue
中的模态,因为这确保它们尽可能位于 DOM 树的最高位置(以确保它们出现在所有内容之上)。
因此,我的 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 内的所有内容之上(特别是具有设置 z-index
的元素),我看不到上述内容的替代方案...
任何人都可以建议一种方法来确保我的模式能够正常工作,即使它们放置在子组件中也是如此?
潜在的解决方案
我确实考虑过以下解决方案,但它看起来很脏......
- 触发
开放模式
事件 - 将相关
modal
组件移动到父App.vue
组件 - 显示模态框
其他信息
如果上述内容不清楚,我试图避免在我的 App.vue
组件中定义所有模式,并允许在任何子组件中定义我的模式组件。
目前我无法执行此操作的原因是模态的 HTML 必须尽可能出现在 DOM 树中的较高位置,以确保它们出现在所有内容之上。
最佳答案
这就是我所说的:
在助手中创建一个 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
是一个典型的 modal component ,但您可以通过关闭 Esc 或 Del 按键等来增强它...
您要在哪里打开模式:
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 */
}
}
话虽这么说,没有人会阻止您创建多个 Modal/Dialog/Popup 组件,要么是因为它可能有不同的模板,要么因为它可能具有重要的附加功能,这会污染通用 Modal
组件(即:LoginModal.vue
、AddReportModal.vue
、AddUserModal.vue
、AddCommentModal.vue
)。
这里的重点是:它们不会添加到应用程序(DOM)中,直到您实际 $mount
它们。您没有将标记放置在父组件中。您可以在开头 fn
中定义要传递哪些 Prop 、要听什么等等...
除了在父级上触发的 unwatch
方法之外,所有事件都绑定(bind)到programmaticComponent 实例,因此没有垃圾。
这就是我所说的,在您打开 DOM 之前,没有真正的隐藏模式实例潜伏在 DOM 上。
甚至不能说这种方法一定比其他方法更好(但它有一些优点)。从我的观点来看,它只是受到 Vue 的灵活性和核心原则的启发,这显然是可能的,并且它允许灵活地 .$mount
并将任何组件(不仅是模态)部署到任何组件上或从任何组件上处理组件。
当您需要从同一复杂应用程序的多个 Angular 落打开同一组件并且您认真对待 DRY 时,它尤其有用。
参见vm.$mount
文档。
关于javascript - 将模态框放置在 Vue.js 的子组件中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57964041/