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

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



作者:追寻1989
链接:https://www.jianshu.com/p/2a11d95ffd2e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐