在使用elementUI的弹窗插件el-dialog时,因为每次打开必须要重新渲染里面的内容,所以使用了destroy-on-close
属性,发现并没有用:
<el-dialog :key="popupType" title="选择用户" :visible.sync="popupShow" :destroy-on-close="true">
<div>
弹窗内容,这里省略一万行。。。
</div>
</el-dialog>
F12打开控制台,发现加不加destroy-on-close属性,关闭dialog,这段代码都存在,而不是消失。
具体原因可以参考以下这个文章:https://www.jianshu.com/p/77d1ba476a6d,重点看下作者介绍的原理即可。
但文章里说的把el-dialog标签写在父组件不写在子组件就也可以让destroy-on-close生效,但实际测试过程中发现这种写法还是无法让元素消失,打开F12还是有元素存在,不是我们想要的效果。
经过测试发现,把el-dialog标签写在父组件不写在子组件再使用destroy-on-close属性,实际它只能初始化dialog组件内部包裹的子组件data数据!!而且子组件的生命周期函数钩子会在关闭弹窗后还会执行一次!!,这样就感觉destroy-on-close很鸡肋。。。因为如果你只是想让表单数据重置,那么这其实相当于你在关闭弹窗的事件写上element表单重置的resetFields(),但如果想要清空数据,初始化也可能不是你想要的清空效果(这里注意:重置不等于清空,重置的是初始化传入的数据,清空是把数据全部置空),可以接着看下下面的小tip:
小Tip:resetFields()无效的办法
如果你是新增和编辑的复用弹窗的情况,你想让打开的弹窗表单数据重置,尤其是想要清空表单数据,当你在关闭弹窗的使用resetFields(),你点击编辑在打开弹窗的时候把数据传入,再点击新增,那么这时候就新增弹窗内还是会显示之前编辑时的数据,感觉resetFields好像不生效,实际正确使用方法应该是先让弹窗打开this.dialogShow = true
再使用this.$nextTick
把数据传入,让数据传入比内置的初始化数据慢一步,这样初始化数据默认就不是你传入的数据。有写过一篇更详细的文章,有此类问题的朋友可以参考:https://www.jianshu.com/p/038580ebf33f
这里提供一份我在线环境测试的各种表单重置数据不会有问题的el-dialog写法demo,可以直接线上调试:
线上调试测试地址:https://codesandbox.io/s/wispy-meadow-yn4yz
一、如果想要把el-dialog标签都写在父组件或者把el-dialog标签整个作为子组件,重置表单(清空)需要配合使用this.$refs.form.resetFields()
和this.$nextTick(()=>{})
1、下面是el-dialog标签都写在父组件的使用:
// 父组件
<template>
<div id="app">
<el-button type="success" @click="handelOpen('add')">新增</el-button>
<el-button @click="handelOpen('edit')">编辑</el-button>
<el-dialog
:title="'弹窗测试-' + (dialogType === 'add' ? '新增' : '编辑')"
:visible.sync="dialogVisible"
:before-close="handleClose"
>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose2">取 消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
dialogVisible: false,
dialogType: "", //弹窗类型
form: {
name: "",
},
};
},
methods: {
handelOpen(val) {
this.dialogVisible = true;
this.dialogType = val;
if (val === "edit") {
// 模拟数据请求
setTimeout(() => {
// this.$nextTick让空数据初始化后再传入,避免传入的数据直接变成初始化数据,导致无法重置(清空数据)
this.$nextTick(() => {
this.form = {
name: "阿布",
};
});
}, 500);
}
},
// 重置表单
handleReset() {
this.$refs.form.resetFields();
},
// 关闭弹窗并重置表单
handleClose2() {
this.handleReset();
this.dialogVisible = false;
},
},
};
</script>
2、下面是el-dialog标签整个作为子组件的使用:
// 父组件
<template>
<div id="app">
<el-button type="success" @click="handelOpen('add')">新增</el-button>
<el-button @click="handelOpen('edit')">编辑</el-button>
<test
v-model="dialogVisible"
:title="'弹窗测试-' + (dialogType === 'add' ? '新增' : '编辑')"
:form="form"
@closeDialog="handleClose"
/>
</div>
</template>
<script>
import test from "./components/test.vue";
export default {
name: "App",
components: {
test,
},
data() {
return {
dialogVisible: false,
dialogType: "", //弹窗类型
form: {
name: "",
},
};
},
methods: {
handelOpen(val) {
this.dialogVisible = true;
this.dialogType = val;
if (val === "edit") {
// 模拟数据请求
setTimeout(() => {
// this.$nextTick让空数据初始化后再传入,避免传入的数据直接变成初始化数据,导致无法重置(清空数据)
this.$nextTick(() => {
this.form = {
name: "阿布",
};
});
}, 500);
}
},
// 关闭弹窗并重置表单
handleClose() {
this.dialogVisible = false;
},
},
};
</script>
但是注意不管是哪种方式,如若需要清空表单数据,test组件传入的表单数据form都需要监听并用另一个变量info接收并进行深拷贝,内外数据才不会相互影响
// src/components/test.vue 子组件
<template>
<div>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:before-close="handleClose"
>
<el-form ref="form" :model="info" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="info.name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "test",
props: {
value: {
type: Boolean,
default: false,
},
title: {
type: String,
default: "",
},
form: {
type: Object,
default: () => ({}),
},
},
computed: {
dialogVisible: {
// 利用计算属性动态设置外部v-model绑定值
set(val) {
this.$emit("input", val);
},
// 利用计算属性动态获取外部v-model绑定值
get() {
return this.value;
},
},
},
watch: {
form(newVal, oldVal) {
console.log("newVal", newVal);
// 深拷贝
this.info = JSON.parse(JSON.stringify(newVal));
},
},
data() {
return {
info: {},
};
},
methods: {
handleReset() {
// 重置
this.$refs.form.resetFields();
},
handleClose() {
// 重置
this.handleReset();
// 关闭弹窗
this.$emit("closeDialog");
},
},
};
</script>
二、如果想要把el-dialog标签写在父组件,内容写在子组件,更好的替代方案:用v-if或者key来实现,解决以上一切问题!
1、下面是key的使用(test为子组件):
<template>
<div id="app">
<el-button type="success" @click="handelOpen('add')">新增</el-button>
<el-button @click="handelOpen('edit')">编辑</el-button>
<!-- 可以充当resetFields重置表单的方法来使用 -->
<!-- 需要注意的是 key绑定的dialogVisible每次开启和关闭变化都会让test子组件生命周期重新加载和data数据初始化-->
<!-- test组件传入的表单数据都需要监听并用另一个变量接收,且不能初始化就触发监听 -->
<el-dialog
:title="'弹窗测试-' + (dialogType === 'add' ? '新增' : '编辑')"
:visible.sync="dialogVisible"
:before-close="handleClose"
>
<test :key="dialogVisible" :form="form" @closeDialog="handleClose" />
</el-dialog>
</div>
</template>
<script>
import test from "./components/test.vue";
export default {
name: "App",
components: {
test,
},
data() {
return {
dialogVisible: false,
dialogType: "", //弹窗类型
form: {
name: "",
},
};
},
methods: {
handelOpen(val) {
this.dialogVisible = true;
this.dialogType = val;
if (val === "edit") {
// 模拟数据请求
setTimeout(() => {
this.form = {
name: "阿布",
};
}, 500);
}
},
// 关闭弹窗
handleClose() {
this.dialogVisible = false;
},
},
};
</script>
2、下面是v-if的使用(test为子组件):
<template>
<div id="app">
<el-button type="success" @click="handelOpen('add')">新增</el-button>
<el-button @click="handelOpen('edit')">编辑</el-button>
<!-- 可以充当resetFields重置表单的方法来使用 -->
<!-- 需要注意的是 v-if绑定的dialogVisible每次关闭会让test子组件生命周期重新加载和data数据初始化-->
<!-- test组件传入的表单数据都需要监听并用另一个变量接收,且不能初始化就触发监听 -->
<el-dialog
:title="'弹窗测试4-' + (dialogType === 'add' ? '新增' : '编辑')"
:visible.sync="dialogVisible"
:before-close="handleClose"
>
<test v-if="dialogVisible" :form="form" @closeDialog="handleClose" />
</el-dialog>
</div>
</template>
<script>
import test from "./components/test.vue";
export default {
name: "App",
components: {
test,
},
data() {
return {
dialogVisible: false,
dialogType: "", //弹窗类型
form: {
name: "",
},
};
},
methods: {
handelOpen(val) {
this.dialogVisible = true;
this.dialogType = val;
if (val === "edit") {
// 模拟数据请求
setTimeout(() => {
this.form = {
name: "阿布",
};
}, 500);
}
},
// 关闭弹窗
handleClose() {
this.dialogVisible = false;
},
},
};
</script>
以上效果等同于使用 destroy-on-close,但不推荐使用 destroy-on-close:
3、下面是destroy-on-close的使用,不推荐(test为子组件):
<template>
<div id="app">
<el-button type="success" @click="handelOpen('add')">新增</el-button>
<el-button @click="handelOpen('edit')">编辑</el-button>
<!-- 这种方法destroy-on-close可以生效-->
<!-- 可以充当resetFields重置表单的方法来使用 -->
<!-- 需要注意的是 destroy-on-close 只会在每次关闭会让test子组件生命周期重新加载和data数据初始化,但元素不会去除-->
<!-- test组件传入的表单数据都需要监听并用另一个变量接收,且不能初始化就触发监听 -->
<el-dialog
:title="'弹窗测试3-' + (dialogType === 'add' ? '新增' : '编辑')"
destroy-on-close
:visible.sync="dialogVisible"
:before-close="handleClose"
>
<test :form="form" @closeDialog="handleClose" />
</el-dialog>
</div>
</template>
<script>
import test from "./components/test.vue";
export default {
name: "App",
components: {
test,
},
data() {
return {
dialogVisible: false,
dialogType: "", //弹窗类型
form: {
name: "",
},
};
},
methods: {
handelOpen(val) {
this.dialogVisible = true;
this.dialogType = val;
if (val === "edit") {
// 模拟数据请求
setTimeout(() => {
this.form = {
name: "阿布",
};
}, 500);
}
},
// 关闭弹窗
handleClose() {
this.dialogVisible = false;
},
},
};
</script>
但是注意不管是v-if或者key或者destroy-on-close哪种方式,如若需要清空表单数据,test组件传入的表单数据form都需要监听并用另一个变量info接收并进行深拷贝,内外数据才不会相互影响,且不能初始化watch就使用immediate:true
就触发监听,才能清空数据:
// src/components/test.vue 子组件
<template>
<div>
<el-form ref="form" :model="info" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="info.name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
</span>
</div>
</template>
<script>
export default {
name: "test",
props: {
form: {
type: Object,
default: () => ({}),
},
},
data() {
return {
info: {},
};
},
watch: {
form(newVal, oldVal) {
console.log("newVal", newVal);
// 深拷贝
this.info = JSON.parse(JSON.stringify(newVal));
},
},
created() {
console.log("created");
},
mounted() {
console.log("mounted");
},
methods: {
handleClose() {
// 关闭弹窗
this.$emit("closeDialog");
},
},
};
</script>
这样的话,既重新渲染了,又保证了动画会执行,所有情况都归纳出来了,解决了每次弹窗不同写法无法重新渲染(更多是想要清空数据)问题的懵逼和尴尬,完美。。