Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Vue】Vue中的生命周期 #83

Open
Easay opened this issue May 13, 2021 · 5 comments
Open

【Vue】Vue中的生命周期 #83

Easay opened this issue May 13, 2021 · 5 comments
Labels

Comments

@Easay
Copy link
Owner

Easay commented May 13, 2021

生命周期

Vue.js实例的生命周期可分为四个阶段:初始化阶段、模板编译阶段、挂载阶段、卸载阶段。
QQ图片20210513164938
QQ图片20210513164951

  • 初始化阶段
    new Vue()created之间的阶段叫初始化阶段。这一阶段主要初始化一些属性、事件以及响应式数据。
  • 模板编译阶段
    这一阶段主要将模板编译成渲染函数,如果在只包含运行时的构建版本中执行new Vue(),则不会存在这个阶段。
  • 挂载阶段
    在该阶段,Vue.js会将实例挂载到DOM元素上,也就是将模板渲染到指定DOM元素中。挂载过程,Vue会开启Watcher来持续追踪依赖的变化。
  • 卸载阶段
    调用vm.$destroy方法后,Vue.js的生命周期进入卸载阶段。在这个阶段,Vue.js会将自身从父组件中删除,取消实例上的所有依赖的追踪并移除事件监听器。

_init方法内部流程图:
init流程图

callHook函数内部原理

Vue.js通过callHook函数来触发生命周期钩子。可以在Vue.js构造函数中通过options参数得到用户设置的生命周期钩子。
所有生命周期钩子函数:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed
  • activated
  • deactivated
  • errorCaptured

Vue会将生命周期的钩子函数放入数组中。为什么要这么做?

使用Vue.mixin设置生命周期钩子mounted后,在执行new Vue()时,会在参数中也设置一个生命周期钩子mounted,这时vm.$options.mounted是一个数组,里面包含两个生命周期钩子。

@Easay Easay added the Vue label May 13, 2021
@Easay
Copy link
Owner Author

Easay commented May 20, 2021

beforeCreate()

从Vue实例开始创建到beforeCreate钩子执行的过程中主要进行了一些初始化操作,例如组件的事件与生命周期钩子的初始化。在此生命周期钩子执行时组件并未挂载,data、methods等也并未绑定,此时主要可以用来加载一些与Vue数据无关的操作,例如展示一个loading等。

console.log("beforeCreate");
console.log(this.$el); //undefined
console.log(this.$data); //undefined 
console.log(this.msg); // undefined
console.log("--------------------");

beforeCreate() 在实例创建之前执行,数据未加载状态

created

从beforeCreate到created的过程中主要完成了数据绑定的配置、计算属性与方法的挂载、watch/event事件回调等。在此生命周期钩子执行时组件未挂载到到DOM,属性$el目前仍然为undefined,但此时已经可以开始操作data与methods等,只是页面还未渲染,在此阶段通常用来发起一个XHR请求。

console.log("created");
console.log(this.$el); //undefined
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Lifecycle
console.log("--------------------");

created() 在实例创建、数据加载后,能初始化数据,dom渲染之前执行

beforeMount

从created到beforeMount的过程中主要完成了页面模板的解析,在内存中将页面的数据与指令等进行解析,当页面解析完成,页面模板就存在于内存中。在此生命周期钩子执行时$el被创建,但是页面只是在内存中,并未作为DOM渲染。

console.log("beforeMount");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); // {__ob__: Observer}
console.log(this.msg); // Vue Lifecycle
console.log("--------------------");

beforeMount() 虚拟dom已创建完成,在数据渲染前最后一次更改数据

mounted

从beforeMount到mounted的过程中执行的是将页面从内存中渲染到DOM的操作。在此生命周期钩子执行时页面已经渲染完成,组件正式完成创建阶段的最后一个钩子,即将进入运行中阶段。此外关于渲染的页面模板的优先级,是render函数 > template属性 > 外部HTML。

console.log("mounted");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Lifecycle
console.log("--------------------");

mounted() 页面、数据渲染完成,真实dom挂载完成

beforeUpdate

当数据发生更新时beforeUpdate钩子便会被调用,此时Vue实例中数据已经是最新的,但是在页面中的数据还是旧的,在此时可以进一步地更改状态,这不会触发附加的重渲染过程。在上述例子中加入了debugger断点,可以观察到Vue实例中数据已经是最新的,但是在页面中的数据还是旧的。

// this.msg = "Vue Update";
console.log("beforeUpdate");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Update
debugger;
console.log("--------------------");

beforeUpadate() 重新渲染之前触发

update

当数据发生更新并在DOM渲染完成后updated钩子便会被调用,在此时组件的DOM已经更新,可以执行依赖于DOM的操作。

// this.msg = "Vue Update";
console.log("updated");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Update
console.log("--------------------");

updated() 数据已经更改完成,dom 也重新 render 完成,更改数据会陷入死循环

beforeDestroy

在Vue实例被销毁之前beforeDestroy钩子便会被调用,在此时实例仍然完全可用。

// this.$destroy();
console.log("beforeDestroy");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Update
console.log("--------------------");

beforeDestory() 和 destoryed() 前者是销毁前执行(实例仍然完全可用),后者则是销毁后执行

destroyed

在Vue实例被销毁之后destroyed钩子便会被调用,在此时Vue实例绑定的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁,组件无法使用,data和methods也都不可使用,即使更改了实例的属性,页面的DOM也不会重新渲染。

// this.$destroy();
console.log("destroyed");
console.log(this.$el); //<div id="app">...</div>
console.log(this.$data); //{__ob__: Observer} 
console.log(this.msg); // Vue Update
console.log("--------------------");

@Easay
Copy link
Owner Author

Easay commented May 20, 2021

一般在哪个生命周期请求异步数据?

异步请求在哪个阶段都可以调用,因为会先执行完生命周期的钩子函数之后,才会执行异步函数,但如果考虑用户体验方面的话,在created中调用异步请求最佳,用户就越早感知页面的已加载,毕竟越早获取数据,在mounted实例挂载的时候就越及时。

可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间;
  • ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

@Easay
Copy link
Owner Author

Easay commented May 24, 2021

image

@Easay
Copy link
Owner Author

Easay commented May 25, 2021

一般在beforeDestroy中做什么?

在 beforeDestroy 中销毁定时器。

为什么销毁它

在页面a中写了一个定时器,比如每隔一秒钟打印一次1,当我点击按钮进入页面b的时候,会发现定时器依然在执行,这是非常消耗性能的。

解决

方案一:

mounted(){
 this.timer = setInterval(()=>{
    console.log(1)
 },1000)
},
beforeDestroy(){
 clearInterval(this.timer)
}

以上方案有两点不好:

  • 它需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。

方案二:通过$once这个事件侦听器在定义完定时器之后的位置来清除定时器

mounted(){
 const timer = setInterval(()=>{
    console.log(1)
 },1000)
 this.$once('hook:beforeDestroy',()=>{
  clearInterval(timer)
 })
}

@Easay
Copy link
Owner Author

Easay commented May 31, 2021

父子组件的生命周期钩子执行顺序:

  • 父组件beforeCreated
  • 父组件created
  • 父组件beforeMounted
  • 子组件beforeCreated
  • 子组件created
  • 子组件beforeMounted
  • 子组件mounted
  • 父组件mounted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant