文:徐超,《React进阶之路》作者
授权发布,转载请注明作者及出处
React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React。
组件是构建React应用的基本单位,组件需要具备数据获取、业务逻辑处理、以及UI呈现的能力,而这些能力是要依赖于组件不同的生命周期方法的。组件的生命周期分为3个阶段:挂载阶段、更新阶段、卸载阶段,每个阶段都包含相应的生命周期方法。因为是深入系列文章,本文不会仔细介绍每个生命周期方法的使用,而是会重点讲解在使用组件生命周期时,经常遇到的疑问和错误使用方式。
初学者在使用React时,常常不知道何时向服务器发送请求,获取组件所需数据。对于组件所需的初始数据,最合适的地方,是在componentDidMount方法中,进行数据请求,这个时候,组件完成挂载,其代表的DOM已经挂载到页面的DOM树上,即使获取到的数据需要直接操作DOM节点,这个时候也是绝对安全的。有些人还习惯在constructor或者componentWillMount中,进行数据请求,认为这样可以更快的获取到数据,但它们相比componentDidMount的执行时间,提前的时间实在是太微乎其微了。另外,当进行服务器渲染时(SSR),componentWillMount是会被调用两次的,一次在服务器端,一次在客户端,这时候就会导致额外的请求发生。
组件进行数据请求的另一种场景:由父组件的更新导致组件的props发生变化,如果组件的数据请求依赖props,组件就需要重新进行数据请求。例如,新闻详情组件NewsDetail,在获取新闻详情数据时,需要传递新闻的id作为参数给服务器端,当NewsDetail已经处于挂载状态时,如果点击其他新闻,NewsDetail的componentDidMount并不会重新调用,因而componentDidMount中进行新闻详情数据请求的方法也不会再次执行。这时候,应该在componentWillReceiveProps中,进行数据请求:
componentWillReceiveProps(nextProps) {
if(this.props.newId !== nextProps.newsId) {
fetchNewsDetailById(nextProps.newsId) // 根据最新的新闻id,请求新闻详情数据
}
}
如果进行数据请求的时机是由页面上的交互行为触发的,例如,点击查询按钮后,查询数据,这时只需要在查询按钮的事件监听函数中,执行数据请求即可,这种情况一般是不会有疑问的。
组件的更新是组件生命周期中最复杂的阶段,也是涉及到最多生命周期方法的阶段。
正常情况下,当组件发生更新时,组件的生命周期方法的调用顺序如下:
componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
// 组件收到新的props(props中的数据并不一定真正发生变化)-> 决定是否需要继续执行更新过程 -> 组件代表的虚拟DOM即将更新 -> 组件重新计算出新的虚拟DOM -> 虚拟DOM对应的真实DOM更新到真实DOM树中
父组件发生更新或组件自身调用setState,都会导致组件进行更新操作。父组件发生更新导致的组件更新,生命周期方法的调用情况同上所述。如果是组件自身调用setState,导致的组件更新,其生命周期方法的调用情况如下:
shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
可见,这种情况下componentWillReceiveProps并不会被调用。
当组件的shouldComponentUpdate返回false时,组件会停止更新过程,这时候生命周期方法的调用顺序如下:
componentWillReceiveProps -> shouldComponentUpdate -> 结束
或(组件自身调用setState,导致的组件更新):
shouldComponentUpdate -> 结束
组件的生命周期方法众多,哪些方法中可以调用setState更新组件状态?哪些方法中不可以呢?
componentWillMount、componentDidMount、componentWillReceiveProps、componentDidUpdate
这里有几个注意点:
其他生命周期方法都不能调用setState,主要原因有两个:
先看下面的一个例子:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
bgColor: "red"
}
}
render() {
var {bgColor} = this.state
return (
<div style = {{backgroundColor: bgColor}}>
Test
</div>
);
}
componentDidMount() {
this.setState({
bgColor: "yellow"
})
}
}
当我们观察浏览器渲染出的页面时,页面中Test所在div的背景色,是先显示红色,再变成黄色呢?还是直接就显示为黄色呢?
答案是:直接就显示为黄色!
这个过程中,组件的生命周期方法被调用的顺序如下:
constructor -> componentWillMount -> render -> componentDidMount -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
组件在挂载完成后,因为setState的调用,将立即执行一次更新过程。虽然render方法被调用了两次,但这并不会导致浏览器界面更新两次,实际上,两次DOM的修改会合并成一次浏览器界面的更新。React官网介绍componentDidMount方法时也有以下说明:
Calling
setState()
in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though therender()
will be called twice in this case, the user won’t see the intermediate state.
这说明,组件render的次数 不一定等于 浏览器界面更新次数。虽然JS的执行和DOM的渲染分别由浏览器不同的线程完成,但JS的执行会阻塞DOM的渲染,而上面的两次render是在一个JS事件周期内执行的,所以在两次render结束前,浏览器不会更新界面。
React 深入系列5:事件处理