实现

<template>
  <div>
    <div ref="moveNodeRef" class="dialog-outter" :style="style">
      <div class="mask"></div>
      <slot></slot>
    </div>
  </div>
</template>
<script>
import {
  getMaxZIndex
} from '@Common/js/dom.js';
export default {
  data() {
    return {
      style: {
        'z-index': 1
      },
      moveNode: null
    }
  },
  props: {
    // 是否添加到body
    appendToBody: {
      type: Boolean,
      default: false
    },
    // parentNode
    parentNode: {
      type: HTMLDivElement,
      default: null
    }
  },
  mounted() {
    this.moveNode = this.$refs.moveNodeRef;
    this.updateDom();
  },
  destroyed() {
    if (this.moveNode && this.moveNode.parentNode) {
      this.moveNode.parentNode.removeChild(this.moveNode);
    }
  },
  watch: {
    parentNode: {
      immediate: true,
      handler(val) {
        this.updateDom();
      }
    },
    appendToBody: {
      immediate: true,
      handler() {
        this.updateDom();
      }
    }
  },
  methods: {
    updateDom() {
      this.$nextTick(() => {
        let moveNodeRef = this.$refs.moveNodeRef;
        if (moveNodeRef) {
          if (this.parentNode) {
            this.parentNode.appendChild(moveNodeRef);
          } else if (this.appendToBody) {
            document.body.appendChild(moveNodeRef);
          }
          let maxZIndex = getMaxZIndex(moveNodeRef.parentNode) + 1000;
          this.style = {
            'z-index': maxZIndex
          };
        }
      });
    }
  }
};

</script>
<style lang='stylus' scoped>
@import '~@Common/stylus/variable.styl';
@import '~@Common/stylus/mixin.styl';
</style>
复制代码

问题

一开始,使用的是element-ui的dialog 但是在使用v-if 的时候会出现报错。 错误的代码也放上来吧。

<template>
	<div>
		<!-- <test-com v-if='a' :key="'a'" :parentNode="parentNode">123</test-com>
		<div>ad</div>
		<test-com v-if='b' :key="'b'" :parentNode="parentNode">456</test-com>
		<div>ck</div> -->
		<el-dialog v-if="a" :visible="true" :key="'cf'" :append-to-body="true">
			<span>这是一段信息</span>
		</el-dialog>
		<el-dialog v-if="b" :visible="true" :key="'cs'" :append-to-body="true">
			<span>222222</span>
		</el-dialog>
		<div @click="cc">dianji a </div>
	</div>
</template>
<script>
	import TestCom from './view/test.vue';
	export default {
		name: 'app',
		components: {
			TestCom
		},
		data() {
			return {
				a: false,
				b: false
			};
		},
		created() {},
		mounted() {
			this.parentNode = document.getElementById('bbb');
			window.cc = this.cc;
		},
		destroyed() {

		},
		methods: {
			cc() {
				if (this.a) {
					this.a = false;
					this.b = true;

				} else {
					this.a = true;
					this.b = false;
				}
			}
		}
	};

</script>
<style lang='stylus' scoped>


</style>

复制代码

当不加:key 的时候,如果两个弹出层不是同步显示出来的话,el-dialog的mounted只会触发一次。后面的不是新生成,而是更新之前的el-dialog。因此需要加上:key 将两个区别开来。但是在连续去切换的时候会出现报错。

// 报错信息
DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to 
be inserted is not a child of this node.
复制代码

从字面意思大概是insertBefore 选取node不是要插入的parentNode的子元素。查看element 的dialog的代码

visible(val) {
    if (val) {
          this.closed = false;
          this.$emit('open');
          this.$el.addEventListener('scroll', this.updatePopper);
          this.$nextTick(() => {
            this.$refs.dialog.scrollTop = 0;
          });
          if (this.appendToBody) {
            document.body.appendChild(this.$el);
          }
        } else {
          this.$el.removeEventListener('scroll', this.updatePopper);
          if (!this.closed) this.$emit('close');
        }
      }
复制代码

他是使用的this.$el 将整个dom元素都插入body中。通过跟踪代码可以发现vue在插入第二个元素的时候,第一个dialog还未销毁,使用他去进行定位插入。而第一个dialog的dom被插入到了body下面,但是前面使用的parentNode还是vue结构树中的父节点,因此第一个dialog 并不在parentNode 下面,因此就出现了上面的报错信息。 这里是vue的insertbefore

// 这里的referenceNode是第一个dialog的dom元素,已经被插入到了body下面了。

而parentNode 却还是vue组件树结构的父元素,
因此会爆出

[Vue warn]: Error in nextTick: "NotFoundError: Failed to execute 'insertBefore' on 'Node': 
The node before which the new node is to be inserted is not a child of this node."
vue.esm.js:1741 DOMException: Failed to execute 'insertBefore' on 'Node': 
The node before which the new node is to be inserted is not a child of this node.
复制代码

这个错误。

解决

取巧的解决方案,不将整个dom全部插入body中,而是在外层再包一层div,将真正要插入的作为div的子元素。这样虽然modeNodeRef会被插入到body中,但是vue在使用inserBefore的时候只会去使用外层的div去定位。

<template>
  <div>
  <!-->这里才是真正要插入的dom<-->
    <div ref="moveNodeRef" class="dialog-outter" :style="style">
      <div class="mask"></div>
      <slot></slot>
    </div>
  </div>
</template>
复制代码

注意点:需要在destoryed中自己去手动删除插入的dom

destroyed() {
    if (this.moveNode && this.moveNode.parentNode) {
      this.moveNode.parentNode.removeChild(this.moveNode);
    }
  },
  watch: {
    parentNode: {
      immediate: true,
      handler(val) {
        this.updateDom();
      }
    },
    appendToBody: {
      immediate: true,
      handler() {
        this.updateDom();
      }
    }
  },
  methods: {
    updateDom() {
      this.$nextTick(() => {
        let moveNodeRef = this.$refs.moveNodeRef;
        if (moveNodeRef) {
          if (this.parentNode) {
            this.parentNode.appendChild(moveNodeRef);
          } else if (this.appendToBody) {
            document.body.appendChild(moveNodeRef);
          }
        }
      });
    }
  }
复制代码

转载于:https://juejin.im/post/5c37052b518825260c5cf0c6

Logo

前往低代码交流专区

更多推荐