Javascript 在Vue.js的子组件中放置模态 背景
通常,在Vue.js组件中使用modals时,通常会创建可重用的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中,因为这样可以确保它
modal
组件,然后使用子组件中的事件控制此组件的状态
例如,考虑下面的代码:
App.vue
注意1:总线是一个单独的Vue实例,它允许在所有组件之间触发事件,而不管它们之间的关系如何
注2:我故意省略了我的模态
组件代码,因为它与问题无关
问题
虽然上述方法非常有效,但有一个关键问题
为了向我的应用程序添加一个modal
,而不是将modal
组件放在引用它的子组件中,我必须将allmodals放在App.vue
中,因为这样可以确保它们在DOM树中尽可能高(以确保它们出现在所有内容之上)
因此,myApp.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中的所有内容之上(特别是带有setz-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 */
}
}