Vue 生命周期挂钩

生命周期挂钩Vue 是组件生命周期中的某些阶段,我们可以在其中添加代码来执行操作。

生命周期挂钩

每当组件到达其生命周期的新阶段时,就会运行一个特定的函数,我们可以向该函数添加代码。此类函数称为生命周期挂钩,因为我们可以 "hook" 我们的代码进入该阶段。

这些是组件具有的所有生命周期挂钩:

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. beforeUnmount
  8. unmounted
  9. errorCaptured
  10. renderTracked
  11. renderTriggered
  12. activated
  13. deactivated
  14. serverPrefetch

以下是这些生命周期挂钩的示例。


“beforeCreate”钩子

这个beforeCreate生命周期钩子发生在组件初始化之前,因此这是在 Vue 设置组件的数据、计算属性、方法和事件侦听器之前。

这个beforeCreate钩子可用于设置全局事件侦听器,但我们应该避免尝试从组件访问属于该组件的元素。beforeCreate生命周期钩子,例如数据、观察者和方法,因为它们在此阶段尚未创建。

此外,尝试从 DOM 元素访问是没有意义的。beforeCreate生命周期钩子,因为它们是在组件创建之后才创建的mounted

示例

CompOne.vue:

<template>
    <h2>Component</h2>
    <p>This is the component</p>
    <p id="pResult">{{ text }}</p>
</template>

<script>
export default {
	data() {
		return {
			text: '...'
		}
	},
  beforeCreate() {
		this.text = 'initial text'; // This line has no effect
    console.log("beforeCreate: The component is not created yet.");
  }
}
</script>

App.vue:

<template>
  <h1>The 'beforeCreate' Lifecycle Hook</h1>
  <p>We can see the console.log() message from 'beforeCreate' lifecycle hook, but there is no effect from the text change we try to do to the Vue data property, because the Vue data property is not created yet.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
#pResult {
  background-color: lightcoral;
  display: inline-block;
}
</style>
运行示例 »

在上面的示例中,第 15 行CompOne.vue没有效果,因为在该行中我们尝试更改 Vue 数据属性内的文本,但 Vue 数据属性实际上尚未创建。另外,记得打开浏览器控制台查看结果console.log()拨打16号线。


“创造”的钩子

这个created生命周期钩子发生在组件初始化之后,因此 Vue 已经设置了组件的数据、计算属性、方法和事件监听器。

我们应该避免尝试从created生命周期钩子,因为在组件被调用之前 DOM 元素是不可访问的mounted

这个created生命周期钩子可用于通过 HTTP 请求获取数据,或设置初始数据值。如下例所示,数据属性“text”被赋予初始值:

示例

CompOne.vue:

<template>
    <h2>Component</h2>
    <p>This is the component</p>
    <p id="pResult">{{ text }}</p>
</template>

<script>
export default {
	data() {
		return {
			text: '...'
		}
	},
  created() {
		this.text = 'initial text';
    console.log("created: The component just got created.");
  }
}
</script>

App.vue:

<template>
  <h1>The 'created' Lifecycle Hook</h1>
  <p>We can see the console.log() message from 'created' lifecycle hook, and the text change we try to do to the Vue data property works, because the Vue data property is already created at this stage.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
#pResult {
  background-color: lightcoral;
  display: inline-block;
}
</style>
运行示例 »

“beforeMount”钩子

这个beforeMount生命周期钩子发生在组件之前mounted,所以就在组件被添加到 DOM 之前。

我们应该避免尝试从beforeMount生命周期钩子,因为在组件被调用之前 DOM 元素是不可访问的mounted

下面的示例显示我们还无法访问组件中的 DOM 元素,即第 11 行CompOne.vue不起作用,并在浏览器控制台中生成错误:

示例

CompOne.vue:

<template>
    <h2>Component</h2>
    <p>This is the component</p>
    <p ref="pEl" id="pEl">We try to access this text from the 'beforeMount' hook.</p>
</template>

<script>
export default {
  beforeMount() {
    console.log("beforeMount: This is just before the component is mounted.");
    this.$refs.pEl.innerHTML = "Hello World!"; // <-- We cannot reach the 'pEl' DOM element at this stage 
  }
}
</script>

App.vue:

<template>
  <h1>The 'beforeMount' Lifecycle Hook</h1>
  <p>We can see the console.log() message from the 'beforeMount' lifecycle hook, but the text change we try to do to the 'pEl' paragraph DOM element does not work, because the 'pEl' paragraph DOM element does not exist yet at this stage.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
#pEl {
  background-color: lightcoral;
  display: inline-block;
}
</style>
运行示例 »

“安装”挂钩

将组件添加到 DOM 树后,mounted()函数被调用,我们可以将代码添加到该阶段。

这是我们第一次有机会做与属于组件的 DOM 元素相关的事情,比如使用ref属性和$refs对象,就像我们在下面的第二个示例中所做的那样。

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>Right after this component is added to the DOM, the mounted() function is called and we can add code to that mounted() function. In this example, an alert popup box appears after this component is mounted.</p>
  <p><strong>Note:</strong> The reason that the alert is visible before the component is visible is because the alert is called before the browser gets to render the component to the screen.</p>
</template>

<script>
export default {
  mounted() {
    alert("The component is mounted!");
  }
}
</script>

App.vue:

<template>
  <h1>The 'mounted' Lifecycle Hook</h1>
  <button @click="this.activeComp = !this.activeComp">Create component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin: 10px;
    width: 400px;
    background-color: lightgreen;
  }
</style>
运行示例 »

笔记:这个mounted阶段发生在组件添加到 DOM 之后,但在上面的示例中alert()在我们看到组件之前就可见。这样做的原因是,首先将组件添加到 DOM,但在浏览器将组件渲染到屏幕之前,mounted阶段发生并且alert()变得可见并暂停浏览器渲染该组件。

下面是一个可能更有用的示例:在安装表单组件后将光标放在输入字段内,以便用户可以开始输入。

示例

CompOne.vue:

<template>
  <h2>Form Component</h2>
  <p>When this component is added to the DOM tree, the mounted() function is called, and we put the cursor inside the input element.</p>
  <form @submit.prevent>
    <label>
      <p>
        Name: <br>
        <input type="text" ref="inpName">
      </p>
    </label>
    <label>
      <p>
        Age: <br>
        <input type="number">
      </p>
    </label>
    <button>Submit</button>
  </form>
  <p>(This form does not work, it is only here to show the mounted lifecycle hook.)</p>
</template>

<script>
  export default {
    mounted() {
      this.$refs.inpName.focus();
    }
  }
</script>
运行示例 »

'beforeUpdate' 钩子

这个beforeUpdate每当组件的数据发生更改时(但在更新呈现到屏幕上之前)都会调用生命周期钩子。这beforeUpdate生命周期钩子发生在updated生命周期挂钩。

一些特别的事情beforeUpdate钩子是我们可以对应用程序进行更改而不触发新的更新,因此我们避免了无限循环。这就是不对应用程序进行更改的原因updated生命周期钩子,因为使用该钩子将创建无限循环。请看下面的第三个示例(红色)。

示例

这个beforeUpdate()函数添加了一个<li>标记到文档以表明beforeUpdate()函数已运行。

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>This is the component</p>
</template>

App.vue:

<template>
  <h1>The 'beforeUpdate' Lifecycle Hook</h1>
  <p>Whenever there is a change in our page, the application is 'updated' and the 'beforeUpdate' hook happens just before that.</p>
  <p>It is safe to modify our page in the 'beforeUpdate' hook like we do here, but if we modify our page in the 'updated' hook, we will generate an infinite loop.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
  <ol ref="divLog"></ol>
</template>

<script>
export default {
  data() {
    return {
      activeComp: true
    }
  },
  beforeUpdate() {
    this.$refs.divLog.innerHTML += "<li>beforeUpdate: This happened just before the 'updated' hook.</li>";
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
</style>
运行示例 »

“更新”的钩子

这个updated生命周期钩子在我们的组件更新其 DOM 树后被调用。

示例

这个updated()函数写入一条消息console.log()。每当页面更新时都会发生这种情况,在本例中是每次添加或删除组件时。

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>This is the component</p>
</template>

App.vue:

<template>
  <h1>The 'updated' Lifecycle Hook</h1>
  <p>Whenever there is a change in our page, the application is updated and the updated() function is called. In this example we use console.log() in the updated() function that runs when our application is updated.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: true
    }
  },
  updated() {
    console.log("The component is updated!");
  }
}
</script>

<style>
#app {
  max-width: 450px;
}
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  width: 80%;
  background-color: lightgreen;
}
</style>
运行示例 »

单击 "Add/Remove Component" 按钮 10 次后,我们可以在浏览器控制台中看到结果:

console screenshot

笔记:我们一定要注意,当页面被修改时,不要修改页面本身。updated调用生命周期钩子,因为页面将一次又一次更新,形成无限循环。

让我们尝试看看如果我们完全按照上面警告我们的方式去做,会发生什么。页面会无限期更新吗?:

示例

这个updated()函数将文本添加到段落中,这又会再次更新页面,并且该函数在无限循环中一次又一次运行。幸运的是,您的浏览器最终会停止这个循环。

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>This is the component</p>
</template>

App.vue:

<template>
  <h1>The 'updated' Lifecycle Hook</h1>
  <p>Whenever there is a change in our page, the application is updated and the updated() function is called.</p>
  <p>The first change that causes the updated hook to be called is when we remove the component by clicking the button. When this happens, the update() function adds text to the last paragraph, which in turn updates the page again and again.</p>
  <button @click="this.activeComp = !this.activeComp">Add/Remove Component</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
  <div>{{ text }}</div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: true,
      text: "Hello, "
    }
  },
  updated() {
    this.text += "hi, ";
  }
}
</script>

<style>
#app {
  max-width: 450px;
}
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  width: 80%;
  background-color: lightgreen;
}
</style>
运行示例 »

当在开发模式下在本地计算机上运行上述代码时,Chrome 浏览器控制台警告如下所示:

screenshot browser console warning

'beforeUnmount' 钩子

这个beforeUnmount生命周期钩子在组件从 DOM 中删除之前被调用。

正如我们在下面的示例中看到的,我们仍然可以在 DOM 中访问组件元素beforeUnmount钩。

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p ref="pEl">Strawberries!</p>
</template>
  
<script>
export default {
  beforeUnmount() {
    alert("beforeUnmount: The text inside the p-tag is: " + this.$refs.pEl.innerHTML);
  }
}
</script>

App.vue:

<template>
  <h1>Lifecycle Hooks</h1>
  <button @click="this.activeComp = !this.activeComp">{{ btnText }}</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: true
    }
  },
  computed: {
    btnText() {
      if(this.activeComp) {
        return 'Remove component'
      }
      else {
        return 'Add component'
      }
    }
  }
}
</script>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin: 10px;
    width: 400px;
    background-color: lightgreen;
  }
</style>
运行示例 »

“未安装”的钩子

这个unmounted生命周期钩子在组件从 DOM 中删除后被调用。

例如,此挂钩可用于删除事件侦听器或取消计时器或间隔。

当一个组件是unmounted, 这unmounted()函数被调用,我们可以向其中添加代码:

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>When this component is removed from the DOM tree, the unmounted() function is called and we can add code to that function. In this example we create an alert popup box when this component is removed.</p>
</template>

<script>
export default {
  unmounted() {
    alert("The component is removed (unmounted)!");
  }
}
</script>

App.vue:

<template>
  <h1>Lifecycle Hooks</h1>
  <button @click="this.activeComp = !this.activeComp">{{ btnText }}</button>
  <div>
    <comp-one v-if="activeComp"></comp-one>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: true
    }
  },
  computed: {
    btnText() {
      if(this.activeComp) {
        return 'Remove component'
      }
      else {
        return 'Add component'
      }
    }
  }
}
</script>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin: 10px;
    width: 400px;
    background-color: lightgreen;
  }
</style>
运行示例 »

笔记:这个unmounted阶段发生在组件从 DOM 中删除之后,但在上面的示例中alert()在组件消失之前可见。这样做的原因是,首先将组件从 DOM 中删除,但在浏览器将组件的删除渲染到屏幕上之前,unmounted阶段发生并且alert()变得可见并暂停浏览器以可见方式删除该组件。


“errorCaptured”钩子

这个errorCaptured当子/后代组件中发生错误时,将调用生命周期钩子。

该钩子可用于错误处理、记录或向用户显示错误。

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>This is the component</p>
  <button @click="generateError">Generate Error</button>
</template>

<script>
export default {
  methods: {
    generateError() {
      this.$refs.objEl.innerHTML = "hi";
    }
  }
}
</script>

App.vue:

<template>
  <h1>The 'errorCaptured' Lifecycle Hook</h1>
  <p>Whenever there is an error in a child component, the errorCaptured() function is called on the parent.</p>
  <p>When the button inside the component is clicked, a method will run that tries to do changes to a $refs object that does not exist. This creates an error in the component that triggers the 'errorCaptured' lifecycle hook in the parent, and an alert box is displayed with information about the error.</p>
  <p>After clicking "Ok" in the alert box you can see the error in the browser console.</p>
  <div>
    <comp-one></comp-one>
  </div>
</template>

<script>
export default {
  errorCaptured() {
    alert("An error occurred");
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
</style>
运行示例 »

有关错误的信息也可以作为参数捕获errorCaptured()函数和这些参数是:

  1. 错误
  2. 触发错误的组件
  3. 错误源类型

在下面的示例中,这些参数被捕获在errorCaptured()函数并写入控制台:

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>This is the component</p>
  <button @click="generateError">Generate Error</button>
</template>

<script>
export default {
  methods: {
    generateError() {
      this.$refs.objEl.innerHTML = "hi";
    }
  }
}
</script>

App.vue:

<template>
  <h1>The 'errorCaptured' Lifecycle Hook</h1>
  <p>Whenever there is an error in a child component, the errorCaptured() function is called on the parent.</p>
  <p>Open the browser console to see the captured error details.</p>
  <div>
    <comp-one></comp-one>
  </div>
</template>

<script>
export default {
  errorCaptured(error,compInst,errorInfo) {
    console.log("error: ", error);
    console.log("compInst: ", compInst);
    console.log("errorInfo: ", errorInfo);
  }
}
</script>

<style>
#app > div {
  border: dashed black 1px;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  background-color: lightgreen;
}
</style>
运行示例 »

“renderTracked”和“renderTriggered”生命周期挂钩

这个renderTracked当渲染函数设置为跟踪或监视反应式组件时,挂钩就会运行。这renderTracked钩子通常在响应式组件初始化时运行。

这个renderTriggered当此类跟踪的反应性组件发生变化时,钩子就会运行,因此会触发新的渲染,以便屏幕更新为最新的更改。

响应式组件是一个可以改变的组件。

渲染函数是一个由 Vue 编译的函数,用于跟踪反应性组件。当反应性组件发生变化时,渲染函数被触发并将应用程序重新渲染到屏幕上。

这个renderTrackedrenderTriggered挂钩旨在用于调试,并且仅在开发模式下可用。

为了看到alert()console.log()来自renderTrackedrenderTriggeredhooks,您必须将下面示例中的代码复制到您的计算机并在开发模式下运行应用程序。

示例

CompOne.vue:

<template>
  <h2>Component One</h2>
  <p>This is a component.</p>
  <button @click="counter++">Add One</button>
  <p>{{ counter }}</p>
</template>
  
<script>
export default {
  data() {
    return {
      counter: 0
    }
  },
  renderTracked(evt) {
    console.log("renderTracked: ",evt);
    alert("renderTracked");
  },
  renderTriggered(evt) {
    console.log("renderTriggered: ",evt)
    alert("renderTriggered");
  }
}
</script>

App.vue:

<template>
  <h1>The 'renderTracked' and 'renderTriggered' Lifecycle Hooks</h1>
  <p>The 'renderTracked' and 'renderTriggered' lifecycle hooks are used for debugging.</p>
  <p><mark>This example only works in development mode, so to see the hooks run, you must copy this code and run it on you own computer in development mode.</mark></p>
  <div>
    <comp-one></comp-one>
  </div>
</template>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin-top: 10px;
    background-color: lightgreen;
  }
</style>
运行示例 »

笔记:上例中的代码旨在以开发模式复制并在您的计算机上本地运行,因为renderTrackedrenderTriggeredhooks 仅适用于开发模式。


“激活”和“停用”生命周期挂钩

正如我们在本页上面看到的,我们有mountedunmounted当组件被删除或添加到 DOM 时的生命周期挂钩。

这个activateddeactivated生命周期挂钩适用于添加或删除缓存的动态组件,但不适用于 DOM。这<KeepAlive>下面的示例中使用 tag 来缓存动态组件。

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>Below is a log with every time the 'mounted' or 'activated' hooks run.</p>
  <ol ref="olEl"></ol>
  <p>You can also see when these hooks run in the console.</p>
</template>
  
<script>
export default {
  mounted() {
    console.log("mounted");
    const liEl = document.createElement("li");
    liEl.innerHTML = "mounted";
    this.$refs.olEl.appendChild(liEl);
  },
  activated() {
    console.log("activated");
    const liEl = document.createElement("li");
    liEl.innerHTML = "activated";
    this.$refs.olEl.appendChild(liEl);
  }
}
</script>

<style>
  li {
    background-color: lightcoral;
    width: 5em;
  }
</style>

App.vue:

<template>
  <h1>The 'activated' Lifecycle Hook</h1>
  <p>In this example for the 'activated' hook we check if the component is cached properly with <KeepAlive>.</p>
  <p>If the component is cached properly with <KeepAlive> we expect the 'mounted' hook to run once the first time the component is included (must be added to the DOM the first time), and we expect the 'activated' hook to run every time the component is included (also the first time).</p>
  <button @click="this.activeComp = !this.activeComp">Include component</button>
  <div>
    <KeepAlive>
      <comp-one v-if="activeComp"></comp-one>
    </KeepAlive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin-top: 10px;
    background-color: lightgreen;
  }
</style>
运行示例 »

让我们扩展上面的示例,看看两者是如何activateddeactivated挂钩工作。我们也使用mountedunmounted挂钩,这样我们就可以看到mounted钩子在第一次添加缓存组件时运行,并且unmounted钩子永远不会为缓存的组件运行。

示例

CompOne.vue:

<template>
  <h2>Component</h2>
  <p>Below is a log with every time the 'activated', 'deactivated', 'mounted' or 'unmounted' hooks run.</p>
  <ol ref="olEl"></ol>
  <p>You can also see when these hooks run in the console.</p>
</template>
  
<script>
export default {
  mounted() {
    this.logHook("mounted");
  },
  unmounted() {
    this.logHook("unmounted");
  },
  activated() {
    this.logHook("activated");
  },
  deactivated() {
    this.logHook("deactivated");
  },
  methods: {
    logHook(hookName) {
      console.log(hookName);
      const liEl = document.createElement("li");
      liEl.innerHTML = hookName;
      this.$refs.olEl.appendChild(liEl);
    }
  }
}
</script>

<style>
  li {
    background-color: lightcoral;
    width: 5em;
  }
</style>

App.vue:

<template>
  <h1>The 'activated' and 'deactivated' Lifecycle Hooks</h1>
  <p>In this example for the 'activated' and 'deactivated' hooks we also see when and if the 'mounted' and 'unmounted' hooks are run.</p>
  <button @click="this.activeComp = !this.activeComp">Include component</button>
  <div>
    <KeepAlive>
      <comp-one v-if="activeComp"></comp-one>
    </KeepAlive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeComp: false
    }
  }
}
</script>

<style scoped>
  div {
    border: dashed black 1px;
    border-radius: 10px;
    padding: 20px;
    margin-top: 10px;
    background-color: lightgreen;
  }
</style>
运行示例 »

“serverPrefetch”生命周期挂钩

“serverPrefetch”挂钩仅在服务器端渲染 (SSR) 期间调用。

解释和创建“serverPrefetch”挂钩的示例需要相对较长的介绍和设置,这超出了本教程的范围。


Vue练习

通过练习测试一下

练习:

The  lifecycle hook is called 
just before a component is removed from the DOM.

开始练习