2021-09-28 el-dialog 的各种正确使用姿势 以及 el-dialog destroy-on-close 无效的解决办法

在使用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>

这样的话,既重新渲染了,又保证了动画会执行,所有情况都归纳出来了,解决了每次弹窗不同写法无法重新渲染(更多是想要清空数据)问题的懵逼和尴尬,完美。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容