图解ES6中的React生命周期
一个React组件的生命周期分为三个部分:初始化阶段、运行中阶段、销毁阶段。
初始化阶段:
static defaultProps
constructor
componentWillMount
render
componentDidMount
运行中阶段:
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps, nextState)
componentWillUpdate(nextProps, nextState)
render
componentDidUpdate
销毁阶段:
componentWillUnmount
一、初始化阶段
1、设置组件的默认属性
1 | static defaultProps = { |
2、设置组件的初始化状态
1 | constructor(props) { |
3、componentWillMount()
组件即将被渲染到页面之前触发
- 在
render()
方法之前
- 1 因为componentWillMount是在render之前执行,所以在这个方法中
setState
不会发生重新渲染(re-render); - 2 这是服务端渲染(
server render
)中唯一调用的钩子(hook); - 3 通常情况下,推荐用
constructor()
方法代替;
4、render()
组件渲染
执行场景
- 1 在
componentWillMount()
方法之后 - 2 在
componentWillReceive(nextProps, nextState)
方法之后
5、componentDidMount()
组件已经被渲染到页面中后触发:此时可以进行开启定时器、向服务器发送请求等操作、此时页面中有了真正的DOM的元素,可以进行DOM相关的操作
- 执行场景
- 在
render()
方法之后
- 在
- 解释
- 1 这个方法会在render()之后立即执行;
- 2 这里可以对DOM进行操作,这个函数之后ref变成实际的DOM(@TODO 表述可能不清晰);
- 3 这里可以加载服务器数据,并且如果使用了redux之类的数据服务,这里可以出发加载服务器数据的action;
- 4 这里可以使用
setState()
方法触发重新渲染(re-render)
;
二、运行中阶段
1、componentWillReceiveProps(nextProps)
组件接收到属性时触发
- 执行场景
- 在已经挂在的组件(mounted component)接收到新props时触发;
- 简单的说是在除了第一次生命周期(componentWillMount -> render -> componentDidMount)之后的生命周期中出发;
- 解释
- 1 如果你需要在
props
发生变化(或者说新传入的props)来更新state
,你可能需要比较this.props
和nextProps
, 然后使用this.setState()
方法来改变this.state
;
- 1 如果你需要在
- 注意
- 1 React可能会在props传入时即使没有发生改变的时候也发生重新渲染, 所以如果你想自己处理改变,请确保比较props当前值和下一次值; 这可能造成组件重新渲染;
- 2 如果你只是调用
this.setState()
而不是从外部传入props
, 那么不会触发componentWillReceiveProps(nextProps)
函数;这就意味着:this.setState()
方法不会触发componentWillReceiveProps()
,props
的改变或者props
没有改变才会触发这个方法;
2、shouldComponentUpdate(nextProps, nextState)
当组件接收到新属性,或者组件的状态发生改变时触发。组件首次渲染时并不会触发
1 | shouldComponentUpdate(newProps, newState) { |
一般我们通过该函数来优化性能:
一个React项目需要更新一个小组件时,很可能需要父组件更新自己的状态。而一个父组件的重新更新会造成它旗下所有的子组件重新执行render()方法,形成新的虚拟DOM,再用diff算法对新旧虚拟DOM进行结构和属性的比较,决定组件是否需要重新渲染
无疑这样的操作会造成很多的性能浪费,所以我们开发者可以根据项目的业务逻辑,在
shouldComponentUpdate()
中加入条件判断,从而优化性能
例如React中的就提供了一个
PureComponent
的类,当我们的组件继承于它时,组件更新时就会默认先比较新旧属性和状态,从而决定组件是否更新。值得注意的是,PureComponent
进行的是浅比较,所以组件状态或属性改变时,都需要返回一个新的对象或数组
- 执行场景
- 在接收到新
props
或state
时,或者说在componentWillReceiveProps(nextProps)
后触发
- 在接收到新
- 解释
- 在接收新的
props
或state
时确定是否发生重新渲染,默认情况返回true
,表示会发生重新渲染
- 在接收新的
- 注意
- 1 这个方法在首次渲染时或者
forceUpdate()
时不会触发; - 2 这个方法如果返回
false
, 那么props
或state
发生改变的时候会阻止子组件发生重新渲染; - 3 目前,如果
shouldComponentUpdate(nextProps, nextState)
返回false
, 那么componentWillUpdate(nextProps, nextState)
,render()
,componentDidUpdate()
都不会被触发;
- 1 这个方法在首次渲染时或者
3、componentWillUpdate(nextProps, nextState)
组件即将被更新时触发
- 执行场景
- 在
props
或state
发生改变或者shouldComponentUpdate(nextProps, nextState)
触发后, 在render()
之前
- 在
- 解释
- 1 这个方法在组件初始化时不会被调用;
- 注意
- 1 千万不要在这个函数中调用this.setState()方法.;
- 2 如果确实需要响应
props
的改变,那么你可以在componentWillReceiveProps(nextProps)
中做响应操作; - 3 如果
shouldComponentUpdate(nextProps, nextState)
返回false
,那么componentWillUpdate()
不会被触发;
4、componentDidUpdate()
组件被更新完成后触发。页面中产生了新的DOM的元素,可以进行DOM操作
- 执行场景
- 在发生更新或
componentWillUpdate(nextProps, nextState)
后
- 在发生更新或
- 解释
- 1 该方法不会再组件初始化时触发;
- 2 使用这个方法可以对组件中的DOM进行操作;
- 3 只要你比较了
this.props
和nextProps
,你想要发出网络请求(nextwork requests)时就可以发出, 当然你也可以不发出网络请求;
- 注意
- 如果
shouldComponentUpdate(nextProps, nextState)
返回false
, 那么componentDidUpdate(prevProps, prevState)
不会被触发;
- 如果
三、销毁阶段
1、componentWillUnmount()
组件被销毁时触发。这里我们可以进行一些清理操作,例如清理定时器,取消Redux的订阅事件等等。
- 在组件卸载(unmounted)或销毁(destroyed)之前
- 这个方法可以让你处理一些必要的清理操作,比如无效的timers、interval,或者取消网络请求,或者清理任何在
componentDidMount()
中创建的DOM元素(elements);
四、栗子
1 | /* 基本顺序 |
五、两种生命周期
1 组件初始化
- 原因
组件第一次建立
- 触发
- componentWillMount -> render -> componentDidMount
2 组件更新 – props change
- 原因
props
发生改变
- 触发
- componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> componentDidUpdate
3 组件更新 – state change
- 原因
this.setState()
使state
发生改变
- 触发
- shoudlComponentUpdate -> componentWillUpdate -> componentDidUpdate
4 组件卸载或销毁
- 原因
组件卸载或销毁
- 触发
- componentWillUnmount
如果使用es5
在组件的整个生命周期中,随着该组件的props或者state发生改变,其DOM表现也会有相应的变化。一个组件就是一个状态机,对于特定地输入,它总返回一致的输出。
一个React组件的生命周期分为三个部分:实例化、存在期和销毁时。
实例化
当组件在客户端被实例化,第一次被创建时,以下方法依次被调用:
1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
5、componentDidMount
当组件在服务端被实例化,首次被创建时,以下方法依次被调用:
1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
componentDidMount 不会在服务端被渲染的过程中调用。
getDefaultProps
对于每个组件实例来讲,这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props(properties的缩写) 值。
1 | var Hello = React.creatClass({ |
也可以在挂载组件的时候设置 props:
1 | var data = [{title: 'Hello'}]; |
或者调用 setProps
(一般不需要调用)来设置其 props:
1 | var data = [{title: 'Hello'}]; |
但只能在子组件或组件树上调用 setProps。别调用 this.setProps 或者 直接修改 this.props。将其当做只读数据。
React通过 propTypes
提供了一种验证 props 的方式,propTypes
是一个配置对象,用于定义属性类型:
1 | var survey = React.createClass({ |
组件初始化时,如果传递的属性和 propTypes
不匹配,则会打印一个 console.warn 日志。如果是可选配置,可以去掉.isRequired。
getInitialState
对于组件的每个实例来说,这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。每一个React组件都有自己的 state,其与 props 的区别在于 state只存在组件的内部,props 在所有实例中共享。
getInitialState 和 getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。
1 | var LikeButton = React.createClass({ |
每次修改 state,都会重新渲染组件,实例化后通过 state 更新组件,会依次调用下列方法:
1、shouldComponentUpdate
2、componentWillUpdate
3、render
4、componentDidUpdate
但是不要直接修改 this.state,要通过 this.setState 方法来修改。
componentWillMount
该方法在首次渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。
render
该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:
- 只能通过 this.props 和 this.state 访问数据(不能修改)
- 可以返回 null,false 或者任何React组件
- 只能出现一个顶级组件,不能返回一组元素
- 不能改变组件的状态
- 不能修改DOM的输出
render方法返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象。react之所以效率高,就是这个原因。
componentDidMount
该方法不会在服务端被渲染的过程中调用。该方法被调用时,已经渲染出真实的 DOM,可以再该方法中通过 this.getDOMNode()
访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode()
)。
1 | var data = [..]; |
由于组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。有时需要从组件获取真实 DOM 的节点,这时就要用到 ref
属性:
1 | var Area = React.createClass({ |
需要注意的是,由于 this.refs.[refName]
属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。
存在期
此时组件已经渲染好并且用户可以与它进行交互,比如鼠标点击,手指点按,或者其它的一些事件,导致应用状态的改变,你将会看到下面的方法依次被调用
1、componentWillReceiveProps
2、shouldComponentUpdate
3、componentWillUpdate
4、render
5、componentDidUpdate
componentWillReceiveProps
组件的 props 属性可以通过父组件来更改,这时,componentWillReceiveProps 将来被调用。可以在这个方法里更新 state,以触发 render 方法重新渲染组件。
1 | componentWillReceiveProps: function(nextProps){ |
shouldComponentUpdate
如果你确定组件的 props 或者 state 的改变不需要重新渲染,可以通过在这个方法里通过返回 false
来阻止组件的重新渲染,返回 `false 则不会执行 render 以及后面的 componentWillUpdate,componentDidUpdate 方法。
该方法是非必须的,并且大多数情况下没有在开发中使用。
1 | shouldComponentUpdate: function(nextProps, nextState){ |
componentWillUpdate
这个方法和 componentWillMount 类似,在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用,注意不要在此方面里再去更新 props 或者 state。
componentDidUpdate
这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。
销毁时
componentWillUnmount
每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。
当再次装载组件时,以下方法会被依次调用:
1、getInitialState
2、componentWillMount
3、render
4、componentDidMount
反模式
在 getInitialState 方法中,尝试通过 this.props 来创建 state 的做法是一种反模式。
1 | //反模式 |
经过计算后的值不应该赋给 state,正确的模式应该是在渲染时计算这些值。这样保证了计算后的值永远不会与派生出它的 props 值不同步。
1 | //正确模式 |
如果只是简单的初始化 state,那么应用反模式是没有问题的。