子组件为什么mounted了两次

先上个codesandbox地址
代码不多,也直接贴上来了

// 父组件
<template>
  <div id="app">
    <div v-if="a">header</div>
    <div>
      <Child1/>
    </div>
    <div v-if="!a">footer</div>
  </div>
</template>
<script>
import Child1 from "./components/Child1.vue";
export default {
  components: {
    Child1
  },
  data() {
    return {
      a: false
    };
  },
  mounted() {
    console.log("parent mounted");
    this.a = !this.a;
  }
};
</script>

// 子组件
<template>
  <div>
    123
  </div>
</template>

<script>
export default {
  name: "Child1",
  mounted() {
    console.log("child mounted");
  }
};
</script>

父组件结构很简单,从上到下一共有三个div,其中第一个和第三个都有个v-if,并且对应的值相反,也就是说这两个div永远只会显示一个。而中间的div里面包裹着一个子组件。然后在父组件的mounted钩子上变换了a的值,使得两个divv-if状态变化。这时我发现子组件的mounted方法又执行了一遍,或者说,子组件被重新创建了。控制台打印的结果是

child mounted 
parent mounted 
child mounted 

接着,我把包裹着子组件的那个div去掉了,让子组件和其余两个div处于同一级,这时我发现,子组件的mounted方法只执行了一次。控制台的打印结果是

child mounted 
parent mounted 

就算在子组件里面再加上一个孙组件,他也只mounted了一次。
我了解了一下vue的diff算法,想试着去解释这个现象,但是还是解释不通,这种情况下,中间的这个节点不应该是作为相同的节点被保留下来的吗?
希望有大神老师可以解答一下我的疑问:
1.我知道v-ifv-show的区别,也知道mountedbeforeMount的区别,我就是不知道在现在的情况下为什么会有这种mounted两次的现象。
2.我不太明白子组件是否被div包裹有什么区别。
3.又试验了一下,如果这里的a的初始值是false,会打印出两次mounted,而初始值是true,子组件就只创建一次。但当这时我给第一个div加上一个随意的内联style,比如style="color:white;",两次mounted就又出现了,(这个原因是diff算法有关,算法判断是否是相同的节点,会看attribute是否一样)这跟节点的先后顺序还有关系?
4.把子组件的mounted换成created效果一样。
5.把父组件的结构改成

 <div v-if="a">header</div>
 <div>
   <Child1/>
 </div>
 <div v-if="a">footer</div>

这时无论a的初始值是什么,都会打印两次mounted。

阅读 15.9k
6 个回答

mounted钩子函数中修改a的值,渲染header <div v-if="a">header</div>时,复用了原来的子组件的外层div。所有子组件要重新渲染一次。
如果a初始是true,此时mounted钩子函数中修改a的值,会先销毁header<div v-if="a">header</div>,不会影响子组件,也不会再次渲染。

不清楚,试了下,把两个 v-if 挨在一起也不会有问题

标签复用的问题吧,可以给三个div加不同的key

是不是在diff时,会对同一父节点下的相同标签元素进行更新

你挂在的子组件Child1,但子组件Child1中又挂载了Child2组件,是这两个组件分别加载一次

渲染两次的原因是在于都是div, vue的diff原理只会比较平级节点,如果平级节点都有子节点,那么比较子节点

都是div,那么就比较子节点
<div>header<div>
<div>child</div>
header文本节点和child组件比较。那么就会删除child组件

推荐问题