关于react.js:谈谈对-React-新旧生命周期的理解

55次阅读

共计 5460 个字符,预计需要花费 14 分钟才能阅读完成。

前言

在写这篇文章的时候,React 曾经出了 17.0.1 版本了,虽说还来探讨目前 React 新旧生命周期有点晚了,React 两个新生命周期尽管出了很久,但理论开发我却没有用过,因为 React 16 版本后咱们间接 React Hook 腾飞开发我的项目。

但对新旧生命周期的摸索,还是有助于咱们更好了解 React 团队一些思维和做法,于是明天就要回顾下这个问题和了解总结,尽管还是 React Hook 写法香,然而仍然要深究学习类组件的货色,理解 React 团队的一些思维与做法。

本文只探讨 React17 版本前的。

React 16 版本后做了什么

首先是给三个生命周期函数加上了 UNSAFE:

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillReceiveProps
  • UNSAFE_componentWillUpdate

这里并不是示意不平安的意思,它只是不倡议持续应用,并示意应用这些生命周期的代码可能在将来的 React 版本(目前 React17 还没有齐全破除)存在缺点,如 React Fiber 异步渲染的呈现。

同时新增了两个生命周期函数:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

UNSAFE_componentWillReceiveProps

UNSAFE_componentWillReceiveProps(nextProps)

先来说说这个函数,componentWillReceiveProps

该子组件办法并不是父组件 props 扭转才触发,官网答复是:

如果父组件导致组件从新渲染,即便 props 没有更改,也会调用此办法。如果只想解决更改,请确保进行以后值与变更值的比拟。

先来说说 React 为什么破除该函数,破除必定有它不好的中央。

componentWillReceiveProps函数的个别应用场景是:

  • 如果组件本身的某个 state 跟父组件传入的 props 密切相关的话,那么能够在该办法中判断前后两个 props 是否雷同,如果不同就依据 props 来更新组件本身的 state。
    相似的业务需要比方:一个能够横向滑动的列表,以后高亮的 Tab 显然隶属于列表本身的状态,但很多状况下,业务需要会要求从内部跳转至列表时,依据传入的某个值,间接定位到某个 Tab。

但该办法毛病是会毁坏 state 数据的繁多数据源,导致组件状态变得不可预测,另一方面也会减少组件的重绘次数。

而在新版本中,官网将更新 state 与触发回调重新分配到了 getDerivedStateFromPropscomponentDidUpdate 中,使得组件整体的更新逻辑更为清晰。

新生命周期办法 static getDerivedStateFromProps(props, state) 怎么用呢?

getDerivedStateFromProps 会在调用 render 办法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

从函数名字就能够看出大略意思:应用 props 来派生 / 更新 state。这就是重点了,凡是你想应用该函数,都必须出于该目标,应用它才是正确且符合规范的。

getDerivedStateFromProps 不同的是,它在挂载和更新阶段都会执行(componentWillReceiveProps挂载阶段不会执行),因为更新 state 这种需要不仅在 props 更新时存在,在 props 初始化时也是存在的。

而且 getDerivedStateFromProps 在组件本身 state 更新也会执行而 componentWillReceiveProps 办法执行则取决于父组件的是否触发从新渲染,也能够看出 getDerivedStateFromProps 并不是 componentWillReceiveProps办法的替代品.

引起咱们留神的是,这个生命周期办法是一个静态方法,静态方法不依赖组件实例而存在,故在该办法外部是无法访问 this 的。新版本生命周期办法能做的事件反而更少了,限度咱们只能依据 props 来派生 state,官网是基于什么考量呢?

因为无奈拿到组件实例的 this,这也导致咱们无奈在函数外部做 this.fetch()申请,或者不合理的 this.setState()操作导致可能的死循环或其余副作用。有没有发现,这都是不合理不标准的操作,但开发者们都有机会这样用。可如果加了个动态 static,间接强制咱们都无奈做了,也从而防止对生命周期的滥用。

React 官网也是通过该限度,尽量放弃生命周期行为的可控可预测,本源上帮忙了咱们防止不合理的编程形式,即一个 API 要放弃单一性,做一件事的理念。

如下例子:

// before
componentWillReceiveProps(nextProps) {if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({isLogin: nextProps.isLogin,});
  }
  if (nextProps.isLogin) {this.handleClose();
  }
}

// after
static getDerivedStateFromProps(nextProps, prevState) {if (nextProps.isLogin !== prevState.isLogin) { // 被比照的 props 会被保留一份在 state 里
    return {isLogin: nextProps.isLogin, // getDerivedStateFromProps 的返回值会主动 setState};
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {if (!prevState.isLogin && this.props.isLogin) {this.handleClose();
  }
}

UNSAVE_componentWillMount

UNSAFE_componentWillMount() 在挂载之前被调用。它在 render() 之前调用,因而在此办法中同步调用 setState() 不会触发额定渲染。

咱们应该防止在此办法中引入任何副作用或事件订阅,而是选用componentDidMount()

在 React 初学者刚接触的时候,可能有这样一个疑难:个别都是数据申请放在 componentDidMount 外面,但放在 componentWillMount 不是会更快获取数据吗?

因为了解是 componentWillMount 在 render 之前执行,早一点执行就早拿到申请后果;然而其实不论你申请多快,都赶不上首次 render,页面首次渲染仍旧处于没有获取异步数据的状态。

还有一个起因,componentWillMount是服务端渲染惟一会调用的生命周期函数,如果你在此办法中申请数据,那么服务端渲染的时候,在服务端和客户端都会别离申请两次雷同的数据,这显然也咱们想看到的后果。

特地是有了 React Fiber,更有机会被调用屡次,故申请不应该放在 componentWillMount 中。

还有一个谬误的应用是在 componentWillMount 中订阅事件,并在 componentWillUnmount 中勾销掉相应的事件订阅。事实上只有调用 componentDidMount 后,React 能力保障稍后调用 componentWillUnmount 进行清理。而且服务端渲染时不会调用componentWillUnmount,可能导致内存泄露。

还有人会将事件监听器(或订阅)增加到 componentWillMount 中,但这可能导致服务器渲染(永远不会调用 componentWillUnmount)和异步渲染(在渲染实现之前可能被中断,导致不调用 componentWillUnmount)的内存透露。

对于该函数,个别状况,如果我的项目有应用,则是通常把现有 componentWillMount 中的代码迁徙至 componentDidMount 即可。

UNSAFE_componentWillUpdate

当组件收到新的 props 或 state 时,会在渲染之前调用 UNSAFE_componentWillUpdate()。应用此作为在更新产生之前执行筹备更新的机会。初始渲染不会调用此办法。

留神,不能在该办法中调用 this.setState();在 componentWillUpdate 返回之前,你也不应该执行任何其余操作(例如,dispatch Redux 的 action)触发对 React 组件的更新。

首先跟下面两个函数一样,该函数也产生在 render 之前,也存在一次更新被调用屡次的可能,从这一点上看就仍然不可取了。

其次,该办法常见的用法是在组件更新前,读取以后某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的解决。但 React 16 版本后有 suspense、异步渲染机制等等,render 过程能够被宰割成屡次实现,还能够被暂停甚至回溯,这导致 componentWillUpdatecomponentDidUpdate 执行前后可能会距离很长时间,这导致 DOM 元素状态是不平安的,因为这时的值很有可能曾经生效了。而且足够使用户进行交互操作更改以后组件的状态,这样可能会导致难以追踪的 BUG。

为了解决这个问题,于是就有了新的生命周期函数:

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate 在最近一次渲染输入(提交到 DOM 节点)之前调用。它使得组件能在产生更改之前从 DOM 中捕捉一些信息(例如,滚动地位)。此生命周期的任何返回值将作为第三个参数传入componentDidUpdate(prevProps, prevState, snapshot)

componentWillUpdate 不同,getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是能够保障与 componentDidUpdate 中统一的。

尽管 getSnapshotBeforeUpdate 不是一个静态方法,但咱们也应该尽量应用它去返回一个值。这个值会随后被传入到 componentDidUpdate 中,而后咱们就能够在 componentDidUpdate 中去更新组件的状态,而不是在 getSnapshotBeforeUpdate 中间接更新组件状态。防止了 componentWillUpdatecomponentDidUpdate 配合应用时将组件长期的状态数据存在组件实例上节约内存,getSnapshotBeforeUpdate 返回的数据在 componentDidUpdate 中用完即被销毁,效率更高。

来看官网的一个例子:

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>
    );
  }
}

如果我的项目中有用到 componentWillUpdate 的话,降级计划就是将现有的 componentWillUpdate 中的回调函数迁徙至 componentDidUpdate。如果触发某些回调函数时须要用到 DOM 元素的状态,则将比照或计算的过程迁徙至 getSnapshotBeforeUpdate,而后在 componentDidUpdate 中对立触发回调或更新状态。

除了这些,React 16 版本的仍然还有大改变,其中引人注目的就是 Fiber,之后我还会抽空写一篇对于 React Fiber 的文章,能够关注我的集体技术博文 Github 仓库,感觉不错的话欢送 star,给我一点激励持续写作吧~

参考:

  • 为什么废除 react 生命周期函数
  • React v16.3 版本新生命周期函数浅析及降级计划

正文完
 0