站点图标 久久日记本

流行前端框架的生命周期

流行前端框架的生命周期

目录

恰巧用过现在比较流行的三大前端框架,AngularJS(Angular), Vue, React,如果能稍微理解一下它们从初始化到加载完成的过程,即生命周期,有助于更好的学习和掌握它们。

从2018年1月26日到今天2018年3月1日,这个主题花了我不少时间。有些框架需要用到,拿来稍微看看文档就上手,忙的时候基本都在写代码,很少有时间仔细看。酝酿了太久,参考了官方一些文档,权且当一回搬运工简单记录一下留给自己,需要的时候倒是可以扫一眼。

效果不太合格,翻译的流畅度有待提高。相当一部分来自官方翻译和github翻译,所有引用都会在本文结束链接,以供参考。

1.AngularJS(NG1)和Angular(NG2+)

AngularJS(NG1)

$onInit()
$onChanges(changesObj)
$doCheck()
$onDestroy()
$postLink()

$onInit()

在元素上的所有controller已经创建并被初始化(并且在此元素上的指令的 pre & post linking 函数之前)之后,这里是放置初始化代码的好地方。

$onChanges(changesObj)

无论是单项绑定(<)还是插值绑定(@),这个函数都会被动执行。

changesObj 是个散列,他们的keys的会随绑定的属性变化而变化,而他们的values是形式上的object { currentValue, previousValue, isFirstChange() }。

使用这个函数来触发组建内的更新,例如clone绑定的值备用以阻止外部值变化影响;即使在数据init绑定时,这个函数也会被触发。

$doCheck()

Digest Cycle每一次循环中都会调用,所谓 Digest Cycle,就是脏数据检查。这个详细的细节,可以参考上面的链接。

在这个过程中你需要加的响应方法都要从该钩子中调用。

例如,如果您希望执行深度相等性检查,或者检查Date对象,AngularJS的变化检测器不会检测到这些变化,因此不会触发$onChanges

这个钩子被调用时没有参数,如果检测到更改,你必须存储先前的值以与当前值进行比较。

$onDestroy()

当包含他的scope被销毁,这个钩子会在controller中被调用。

使用这个钩子释放external resources, watchesevent handlers

组件会有它们自己的$onDestroy()钩子触发事件顺序,如同$scope.$broadcast事件被触发(注意这是自顶向下的)。

Note that components have their $onDestroy() hooks called in the same order as the $scope.$broadcast events are triggered, which is top down.

(自己翻译太拗口,能看到但是实在不知道怎么表达。)

这就意味着父组件在子组件之前调用它们的$onDestroy()

$postLink()

在此controller的element及其子元素已被linked后调用。

类似post-link方法,这个钩子(hook)可以用于设置DOM事件处理程序并执行直接DOM操作。

请注意,包含templateUrl指令的子元素不会被compiled编译linked,因为它们正等待它们的模板异步加载并且它们自己的compilation and linking已经被暂停,直到发生。

这段话原文看起来也许更好理解。

that child elements that contain templateUrl directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs.

从代码的角度,$onInit()在页面初始化数据会用到,其它的钩子一般情况下用的比较少。

花了一些时间搭了一个webpack + ng1 component + typescriptlife-cycle的demo,演示了简单的生命周期和调用顺序过程。你可以在本文最后看到源码链接。

Angular(NG2+)

ng2我只写过一个简单的demo,也是参考了一下starter。不过类比ng1的1.6版本的component,写法可以说做到类似了。

目前ng2的稳定版本是v5.2.6

和ng1相比,ng2的生命周期钩子其实看起来也是哪些,大同小异。

(如果你看了后面的vue和react的生命周期,也会惊叹,为什么差不多呀。)

constructor  (ES6引入类的概念后新出现的东东,是类的自身属性,并不属于Angular的范畴,所以Angular无法控制)
ngOnChanges
ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngOnDestroy

下面翻译来自ng中文站

当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法:

ngOnChanges()

当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的SimpleChanges对象

当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在ngOnInit()之前。

ngOnInit()

在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。

在第一轮ngOnChanges()完成之后调用,只调用一次。

ngDoCheck()

检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。

在每个Angular变更检测周期中调用,ngOnChanges()和ngOnInit()之后。

ngAfterContentInit()

当把内容投影进组件之后调用。

第一次ngDoCheck()之后调用,只调用一次。

只适用于组件。

ngAfterContentChecked()

每次完成被投影组件内容的变更检测之后调用。

ngAfterContentInit()和每次ngDoCheck()之后调用

只适合组件。

ngAfterViewInit()

初始化完组件视图及其子视图之后调用。

第一次ngAfterContentChecked()之后调用,只调用一次。

只适合组件。

ngAfterViewChecked()

每次做完组件视图和子视图的变更检测之后调用。

ngAfterViewInit()和每次ngAfterContentChecked()之后调用。

只适合组件。

ngOnDestroy()

当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。

在Angular销毁指令/组件之前调用。

接口可选:

Angular会去检测我们的指令和组件的类,一旦发现钩子方法被定义了,就调用它们。 Angular会找到并调用像ngOnInit()这样的钩子方法,有没有接口无所谓。

虽然如此,我们还是强烈建议你在TypeScript指令类中添加接口,以获得强类型和IDE等编辑器带来的好处。

关于ng2生命周期的demo,官方文档给了很多详细的代码实例,链接会在本文最后列出。

2.Vue

用Vue写过几个站,我同样会在本文最后链接一个之前写的小站地址以供学习Vue的生命周期。

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。

这意味着 你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。

这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured 

官方图片画的很细了:

beforeCreate

在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

created

在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用。

该钩子在服务器端渲染期间不被调用。

mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted。

beforeUpdate

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。

你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

该钩子在服务器端渲染期间不被调用。

updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。

注意 updated 不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick 替换掉 updated。

activated

keep-alive 组件激活时调用。

该钩子在服务器端渲染期间不被调用。

deactivated

keep-alive 组件停用时调用。

该钩子在服务器端渲染期间不被调用。

beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用。

该钩子在服务器端渲染期间不被调用。

destroyed

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

errorCaptured

当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

你可以在此钩子中修改组件的状态。因此在模板或渲染函数中设置其它内容的短路条件非常重要,它可以防止当一个错误被捕获时该组件进入一个无限的渲染循环。

3.React

在前些年学习React时写过一博文和demo,去年学习ReactNative时,也写过几个小应用,总体看来,其生命周期和另外两种前端框架相比,依然是大同小异。

每个组件都有几个“生命周期方法”,您可以重写以在该进程中的特定时间运行代码。will在事件发生之前调用带有前缀的方法,did在事件发生之后调用带有前缀的方法。

1.Mounting

当创建一个组件的实例并将其插入到DOM中时,将调用这些方法:

constructor()
componentWillMount()
render()
componentDidMount()

2.Updating

更新可能由props或state的更改引起。当一个组件被重新渲染时,这些方法被调用:

componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()

3.Unmounting

从DOM中删除组件时调用此方法:

componentWillUnmount()

4.Error Handling

渲染期间,生命周期方法或任何子组件的构造函数中发生错误时都会调用此方法。

componentDidCatch()

render()

render()

constructor()

React组件的构造函数在挂载之前被调用。

在实现React.Component子类的构造函数时,应该调用super(props)在设置statement之前。否则,this.props在构造函数中将是undefined,这可能会导致错误。

constructor(props) {
  super(props);
  this.state = {
    color: props.initialColor
  };
}

componentWillMount()

componentWillMount()

componentWillMount()mounting发生之前被调用。

render()方法前被调用,因此setState()在此方法中同步调用不会触发额外的rendering

一般来说,我们建议使用constructor()来代替它。

避免在此方法中引入任何副作用或订阅。

对于这些用例,用componentDidMount()来代替。

这是唯一一个在服务器渲染上调用的生命周期钩子。

componentDidMount()

componentDidMount()

在一个组件被mounted后,componentDidMount()会被立即调用,需要的初始化的DOM节点和加载远程数据的方法应该放在这里。

可以在该方法中设置订阅,假如你添加了订阅,需要在componentWillUnmount()方法中unsubscribe

调用setState()方法将触发额外的渲染,但这在浏览器更新屏幕之前发生。

这保证了即使在这种情况下render()也被调用两次,用户不会看到中间状态。

它经常会导致性能问题,所以请谨慎使用此模式。

留下一段不太好理解的段落:

It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.

componentWillReceiveProps()

componentWillReceiveProps(nextProps)

componentWillReceiveProps()在一个mounted component接收新的props之前调用。

假如你需要在响应中更新stateprops(例如:重置它),你可能需要在这个方法里使用this.setState()对比this.propsnextProps和执行状态转换。

即使the props没有更改,react也会调用该方法,所以务必比较当前值和下一个值。当父组件使你的组件re-render,这种情况可能会发生。

mounting期间,初始化props时,react不会调用componentWillReceiveProps()。它只会在一些组件的props更新时才会调用。调用this.setState()通常不会触发componentWillReceiveProps()

shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

当接收到新的propsstateshouldComponentUpdate()rendering之前被调用。在初始渲染或何时forceUpdate(),此方法不会被调用。

componentWillUpdate()

componentWillUpdate(nextProps, nextState)

componentWillUpdate()在接收new propsstate渲染之前被调用。使用此作为更新发生之前执行准备的机会。此方法不用于初始渲染。

Use this as an opportunity to perform preparation before an update occurs.

请注意,你不能在这里调用this.setState(); 你也不应该做任何其他事情(例如:dispatch a Redux action),它会在componentWillUpdate()返回之前触发一个React组件的更新。

如果您需要更新state以响应props更改,使用componentWillReceiveProps()代替它。

componentDidUpdate()

componentDidUpdate(prevProps, prevState)

updating发生之后,componentDidUpdate会被立即调用,此方法不用于初始渲染。

当组件更新时,作为一个操作DOM的机会。

这是一个做request请求的好地方,只要你将当前的props和以前的props进行比较(例如:假如props没有改变,就没有必要做request请求了)。

componentWillUnmount()

componentWillUnmount()

在一个组件unmounteddestroyed之前,componentWillUnmount()会立即被调用。

在这个方法中可以执行必要的清理,例如定时器失效,取消网络请求或清理在其中创建的任何订阅。

componentDidCatch()

componentDidCatch(error, info)

该方法可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示a fallback UI而不是崩溃的组件树。

如果类组件定义了此生命周期方法,则它将成为错误边界。在它里面调用setState()可以让您在下面的树中捕获未处理的JavaScript错误并显示a fallback UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。(Only use error boundaries for recovering from unexpected exceptions; don’t try to use them for control flow.)

setState()

setState(updater[, callback])

setState()将更改排入组件状态,并告诉React该组件及其子组件需要使用更新的状态重新呈现。这是您用来更新用户界面以响应事件处理程序和服务器响应的主要方法。

forceUpdate()

component.forceUpdate(callback)

默认情况下,当你的组件的stateprops改变时,你的组件将重新渲染。如果你的render()方法依赖于其他一些数据,你可以通过调用告诉React组件需要重新渲染forceUpdate()

调用forceUpdate()将导致render()在组件上被调用,跳过shouldComponentUpdate()

这将触发子组件的正常生命周期方法,包括每个子组件的shouldComponentUpdate()方法。 如果the markup更改,React仍然只会更新DOM。

通常你应该尽量避免使用forceUpdate(),并且只能从this.propsthis.state中读取render()

4.Backbone

之前写过BackboneJS,这里提一下,BackboneJS并不存在生命周期 @_@ 手动更新...

这里,你可以参考我的sheep项目简单的感受一下,当前,它并不是BackboneJS写的。

5.源码

分别准备了几个demo放到了github上, 配合上之前写的一些,可以跑一下各个前端框架的生命周期:

AngularJS(ng1)和Angular(ng2+)

life-cycle-angularjs

我在这个totoro多个分支用到了不同的版本,有些也是参考github上一些项目,挖了个坑。

totoro

关于ng2的生命周期,官方给了更多详细的生命周期demo:

ng2-lifecycle-examples

Vue

ant-colony

React

life-cycle-react

react-demos

6.参考资料

angularjs1.6-life-cycle-hooks

关于ng2的生命周期钩子,官方文档讲的非常非常仔细,还带有代码例子:

官方:angular-lifecycle-hooks

ng2中文站,这篇虽然相对官方文档略旧点,不过ng2后面变化很少,只是废弃和更新一些api,大抵相近。

中文翻译版:angular2+生命周期钩子

官方:vue2生命周期钩子

官方:react the component lifecycle

官方:react各个生命周期钩子详解

浅谈使用backbone实现App生命周期

文中提到了Redux,这里先来个链接,还没有详细看,等我补完功课。 Redux不见不散。

关于Redux,这儿有个中文文档

2018-03-09 更新:

Angular之constructor和ngOnInit差异及适用场景

退出移动版