<返回更多

React生命周期详解(新版)

2023-06-15    尚硅谷教育
加入收藏

本篇文章带大家看看React新版生命周期带来了哪些变化。

React16.4版本之后使用了新的生命周期,它使用了一些新的生命周期钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate),并且即将废弃老版的3个生命周期钩子(componentWillMount、componentWillReceiveProps、componentWillUpdate)。

一、新版生命周期

如图所示,我们可以看到,在组件第一次挂载时会经历:

构造器(constructor)=》修改state属性(getDerivedStateFromProps)=》组件挂载渲染(render)=》组件挂载完成(componentDidMount)

组件内部状态更新:

更新state属性(getDerivedStateFromProps)=》判断组件是否更新(shouldComponentUpdate)=》组件更新渲染(render)=》(getSnapshotBeforeUpdate)=》组件更新完成(componentDidUpdate)

组件卸载时执行:

组件销毁(componentWillUnmount)

注意:

新版本使用了getDerivedStateFromProps代替了componentWillMount、componentWillReceiveProps、componentWillUpdate三个钩子函数,如果在旧版本中使用将会警告提示。它必须要return一个null或者对象,并且会影响初始化的值以及修改的值。

getSnapshotBeforeUpdate钩子必须与componentDidUpdate搭配使用否则会报错。在旧版本中使用将会警告提示。必须要return一个null或者任何值,它将在最近一次渲染输出(提交到DOM节点)之前调用。

二、生命周期新增函数详解

static getDerivedStateFromProps(getDSFP)

首先这个新的方法是一个静态方法,在这里不能调用this,也就是一个纯函数。它传了两个参数,一个是新的nextProps ,一个是之前的prevState,所以只能通过prevState而不是prevProps来做对比,它保证了state和props之间的简单关系以及不需要处理第一次渲染时prevProps为空的情况。也基于以上两点,将原本componentWillReceiveProps里进行的更新工作分成两步来处理,一步是setState状态变化,更新 state在getDerivedStateFromProps里直接处理。

旧的React中componentWillReceiveProps方法是用来判断前后两个props是否相同,如果不同,则将新的props更新到相应的state上去。在这个过程中我们实际上是可以访问到当前props的,这样我们可能会对this.props做一些奇奇怪怪的操作,很可能会破坏state数据的单一数据源,导致组件状态变得不可预测。

而在getDerivedStateFromProps中禁止了组件去访问 this.props,强制让开发者去比较nextProps与prevState中的值,以确保当开发者用到getDerivedStateFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去访问this.props并做其他一些让组件自身状态变得更加不可预测的事情。

getSnapshotBeforeUpdate

在React开启异步渲染模式后,在执行函数时读到的DOM元素状态并不一定和渲染时相同,这就导致在componentDidUpdate中使用的DOM元素状态是不安全的(不一定是最新的),因为这时的值很有可能已经失效了。

与componentWillMount不同的是,getSnapshotBeforeUpdate会在最终确定的render执行之前执行,也就是能保证其获取到的元素状态与componentDidUpdate中获取到的元素状态相同。

这个方法并不常用,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。并且会返回snapshot的值或null。例如:

class ScrollingList extends React.Component {

constructor(props) {

super(props);

this.listRef = React.createRef();

}

getSnapshotBeforeUpdate(prevProps, prevState) {

// 我们是否在 list 中添加新的 items ?

// 捕获滚动位置以便我们稍后调整滚动位置。

if (prevProps.list.length < this.props.list.length) {

const list = this.listRef.current;

return list.scrollHeight - list.scrollTop;

}

return null;

}

componentDidUpdate(prevProps, prevState, snapshot) {

// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,

// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。

//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)

if (snapshot !== null) {

const list = this.listRef.current;

list.scrollTop = list.scrollHeight - snapshot;

}

}

render() {

return (

<div ref={this.listRef}>{/* ...contents... */}</div>

);

}

}

上述例子中重点是从 getSnapshotBeforeUpdate读取scrollHeight属性,因为“render”阶段生命周期(如 render)和“commit”阶段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之间可能存在延迟。

生命周期修改的深层原因

因为React 16引入了Fiber机制,把同步的渲染流程进化为了异步的渲染流程,这么做的原因是同步渲染流程有个弊端:一旦开始就不能停下,大工作量的渲染任务执行时,主线程会被长时间的占用,浏览器无法即时响应与用户的交互。

Fiber机制会把渲染任务拆解为多个小任务,并且每执行完一个小任务,就把主线程的执行权交出去,也就解决了上面的弊端。

然而,采用Fiber机制进行渲染时,render阶段没有副作用,可以被暂停,终止或重新启动。就是这个重新启动,会导致工作在render阶段的componentWillMount、componentWillReceiveProps、componentWillUpdate存在重复执行的可能,所以它们几个必须被替换掉。

三、Error boundaries

在React16之前,组件内的JS错误会导致React的内部状态被破坏,并且在下一次渲染时产生无法追踪的错误。这些错误基本上是由其他的非React组件代码错误引起的。但React并没有提供一种优雅的错误处理方式,也无法从错误中恢复。

然而部分UI的JS错误不应该导致整个应用的崩溃,为了解决这个问题,React引入了一个新的概念——错误边界。

Error boundaries(错误边界)是一个React组件,它可以捕获并打印发生在其子组件数任何位置的JS错误,然后,渲染出备用UI,而非崩溃了的子组件。错误边界可以在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

但是错误边界无法捕获以下几种错误:

新版React给我们提供了两个与错误处理相关的API:static getDerivedStateFromError和componentDidCatch。

只要在Class组件中定义static getDerivedStateFromError或componentDidCatch这两个生命周期方法中的任意一个/两个,那么这个组件就是一个错误边界组件了。

static getDerivedStateFromError

getDerivedStateFromError是一个静态方法。在渲染子组件的过程中,页面更新之前,当发生了错误时,该方法就会运行。

它将抛出的错误作为参数并返回一个对象覆盖掉当前组件的state。

class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) { // 更新 state 使下一次渲染可以显降级 UI return { hasError: true }; }

render() {

if (this.state.hasError) { // 你可以渲染任何自定义的降级 UI return <h1>Something went wrong.</h1>; }

return this.props.children;

}

}

componentDidCatch

componentDidCatch会在后代组件抛出错误时调用,因为它的执行时间比较晚,所以一般不支持改变组件state。

它接受两个参数:

error:抛出的错误;

info: 带有componentStack key的对象,其中包含有关组件引发错误的栈信息。

class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) {

// 更新 state 使下一次渲染可以显示降级 UI

return { hasError: true };

}

componentDidCatch(error, info) { // "组件堆栈" 例子: // in ComponentThatThrows (created by App) // in ErrorBoundary (created by App) // in div (created by App) // in App logComponentStackToMyService(info.componentStack); }

render() {

if (this.state.hasError) {

// 你可以渲染任何自定义的降级 UI

return <h1>Something went wrong.</h1>;

}

return this.props.children;

}

}

四、总结

React 16基于两个原因做出了生命周期的调整:

 

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>