当前位置:   article > 正文

React生命周期中有哪些坑?如何避免?_componentdidmount和render

componentdidmount和render

在讨论React 的生命周期的时候,一定是在讨论类组件,因为函数组件并没有生命周期的概念,它本身就是一个函数,只会从头执行到尾巴

其实生命周期只是一个抽象的概念,大部分人看到生命周期想到的往往都componentDidMount,componentWilMount等等函数,然而这些其实并不是它的生命周期,只是在生命周期中按顺序执行的函数而已,挂载 --> 更新 --> 卸载 这一React的完整流程才叫生命周期

挂载阶段 -- 指的是组件从初始化到完成加载的过程

constructor

constructor 是类通用的构造函数,常用于初始化,所以在过去,constructor 常用于初始化state和绑定函数,例如:

  1. import React from 'react';
  2. class Counter extends React.Component {
  3. constructor(props) {
  4. super(props)
  5. this.state = {
  6. count: 0,
  7. }
  8. this.handleClick = this.handleClick.bind(this)
  9. }
  10. handleClick() {
  11. // do some thing
  12. }
  13. render() {
  14. return null
  15. }
  16. }

那为什么会强调过去呢,因为当类属性开始流行之后,React 社区的写法发生了变化,即去除了 constructor。

  1. import React from 'react'
  2. class Counter extends React.Component {
  3. state = {
  4. count: 0,
  5. }
  6. // 类属性第三阶段提案
  7. handleClick = () => {
  8. // do some thing
  9. }
  10. render() {
  11. return null
  12. }
  13. }

社区去除 constructor 的原因很明确:

  • constructor 中并不推荐去处理初始化以外的逻辑
  • constructor 本身并不是属于React的生命周期,它只是Class的一个初始化函数
  • 通过移除constructor, 代码会变的更加的简洁

getDerivedStateFromProps

这个函数的作用是当 props 发生变化时来更新state,那么它的触发时机是什么时候呢?

  • 当 props 被传入的时候
  • 当 state 发生变化时
  • 当 forceUpdate 被调用时

最常见的一个错误就是误认为只有当props发生变化时,getDerivedStateFromProps 才会被调用,而实际上只要父组件重新渲染时,getDerivedStateFromProps 就会被调用,所以是外部参数,

也就是 props 传入时就会发生变化。下面是官方文档中的列子:

  1. class Example extends React.Component {
  2. state = {
  3. isScrollingDown: false,
  4. lastRow: null
  5. };
  6. static getDerivedStateFromProps(props, state) {
  7. if (props.currentRow !== state.lastRow){
  8. return {
  9. isScrollingDown: props.currentRow > state.lastRow,
  10. lastRow: props.currentRow,
  11. }
  12. }
  13. // 返回 null 表示无需要更新state
  14. return null
  15. }
  16. }

 依据官方的说法,它的使用场景是很有限的。

两种反模式的使用方式:

  1. 直接复制到 props 到 state
  2. 在 props 变化后修改 state

这两种写法除了增加代码的维护成本外,没有带来任何的好处。

UNSAFE_componentWillMount

也就是componentWillMount,在组件即将被挂载之前执行某些操作,目前已被弃用,因为在React的新的异步渲染机制下,该方法可能会被多次调用。

写过服务端渲染的同学应该会遇到过, componentWillMount 跟服务器端同构渲染的时候,如果在该函数中发起网络请求话,会发现请求在服务器和客户端各执行了一次,所以React 更推荐在componentDidMount 中去做网络请求等操作

render

render 函数返回的是 JSX 的数据结构,用于描述具体的渲染内容,但是 render函数并不会去真正的渲染组件,真正的渲染是依靠 React 操作 JSX 描述结构来完成,而且 render 函数应该是一个纯函数,不应该在里面产生副作用,比如调用 setState 函数(render 函数在每次渲染的时候都会被调用,而 setState 又会触发渲染,所以就会造成死循环)

componentDidMount

componentDidMount 用于在组件挂载完成时去做某些操作,比如发起网络请求等,componentDidMount 是在render之后被调用

至此 挂载阶段 基本算是完成

更新阶段

更新阶段是指:当外部 props 被传入,又或者当 state 发生改变时的阶段。

UNSAFE_componentWillReceiveProps

该函数已标记弃用,因为其功能可被 getDerviedStateFromProps 所替代

另外,当 getDerviedStateFromProps 存在时 UNSAFE_componentWillReceiveProps 不会被调用

getDerviedStateFromProps

同挂载阶段的表现一致。

shouldComponentUpdate

该方法通过返回true或者fasle来确定是否重新触发新的渲染,这也是性能优化的必争之地,通过添加判断条件来控制组件是否需要重新渲染

当前 React 的官方也给出了一个通用的优化方案,那就是PureComponent,PureComponent 的核心原理就是默认实现了一个 shouldComponentUpdate 函数,在这个函数中对 props 和 state 进行浅比较,用来判断是否重新触发更新。

  1. shouldComponentUpdate(nextProps, nextState) {
  2. // 浅比较仅比较值与引用,并不会对 Object 中的每一项值进行比较
  3. if (shadowEqual(nextProps, this.props) || shadowEqual(nextState, this.state) ) {
  4. return true
  5. }
  6. return false
  7. }

UNSAFE_componentWillUpdate

该方法也被标记弃用,因为在后续的 React 的异步渲染设计中,可能会出现组件暂停更新渲染的情况

render

同挂载阶段的表现一致

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 方法是配合 React 新的异步渲染的机制,在DOM更新发生前被调用,其返回值将作为 componentDidUpate 的第三个参数

官方案例:

  1. class ScrollingList extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.listRef = React.createRef();
  5. }
  6. getSnapshotBeforeUpdate(prevProps, prevState) {
  7. // Are we adding new items to the list?
  8. // Capture the scroll position so we can adjust scroll later.
  9. if (prevProps.list.length < this.props.list.length) {
  10. const list = this.listRef.current;
  11. return list.scrollHeight - list.scrollTop;
  12. }
  13. return null;
  14. }
  15. componentDidUpdate(prevProps, prevState, snapshot) {
  16. // If we have a snapshot value, we've just added new items.
  17. // Adjust scroll so these new items don't push the old ones out of view.
  18. // (snapshot here is the value returned from getSnapshotBeforeUpdate)
  19. if (snapshot !== null) {
  20. const list = this.listRef.current;
  21. list.scrollTop = list.scrollHeight - snapshot;
  22. }
  23. }
  24. render() {
  25. return (
  26. <div ref={this.listRef}>{/* ...contents... */}</div>
  27. );
  28. }
  29. }

componentDidUpdate

如上述案例,getSnapshotBeforeUpdate 的返回值会作为 componentDidUpdate 的第三个参数适用。

copinentDidUpdate 中可以设置 setState,会触发渲染,慎用,避免死循环

至此 更新阶段 基本算是完成

卸载阶段

React 的卸载阶段就容易多啦,只有一个函数

componentWillUnmount

该函数的作用就是用来清理工作,如果项目中有用到定时器啥的,一定记得要在这清除掉,否则就会一直执行~

职责梳理

在梳理了生命周期后,需要注意两个问题

  • 什么情况下会触发重新渲染
  • 渲染过程中的报错该如何处理

带着上述两个问题,下面咱们来一一分析3种重新渲染和错误处理的情况

函数组件

函数组件在任何情况下都会重新渲染,它并没有生命周期,但是官方提供了一种优化的方式,那就是 React.memo 函数

  1. const MyComponent = React.memo(function MyComponent(props) {
  2. /* 使用 props 渲染 */
  3. });

React.memo 并不会阻断渲染,而是跳过渲染组件的操作并直接复用最近一次渲染的结果,这与 shouldComponentUpdate 是完全不同的

React.Component

如果不实现 shouldComponentUpdate 函数,那么下面这两种情况回引发重新渲染

  1. 当 state 发生变化时
  2. 当父组件的 Props 传入时,无论 props 有没有发生变化, 只要传入就会引发重新渲染

React.PureComponent

PureComponent 默认实现了 shouldComponentUpdate 函数。所以仅在 props 与 state 进行浅比较后,确认有变更时才会触发重新渲染。

错误边界

错误边界其实就是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染备用组件,如下官方案例

  1. class ErrorBoundary extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { hasError: false };
  5. }
  6. static getDerivedStateFromError(error) {
  7. // 更新 state 使下一次渲染能够显示降级后的 UI
  8. return { hasError: true };
  9. }
  10. componentDidCatch(error, errorInfo) {
  11. // 同样可以将错误日志上报给服务器
  12. logErrorToMyService(error, errorInfo);
  13. }
  14. render() {
  15. if (this.state.hasError) {
  16. // 可以自定义降级后的 UI 并渲染
  17. return <h1>Something went wrong.</h1>;
  18. }
  19. return this.props.children;
  20. }
  21. }

但是渲染时的报错,只能通过componentDidCatch 捕获,这是在做线上页面报错监控时,极其容易忽略的点。

综上所述,咱们就可以总结这个问题了,如下:

避免生命周期中的坑需要做好两件事:

1、不在恰当的时候调用了不该调用的代码;

2、在需要调用时,不要忘了调用。

那么下面7种情况最容易造成生命周期的坑:

  • getDerivedStateFromProps 容易编写反模式代码,使受控组件和非受控组件区分模糊
  • componentWillMount 在 React 中已被标记弃用,不推荐使用,主要的原因是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定应该放到 componentDidMount 中
  • componentWillReceiveProps 同样也被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
  • shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。
  • componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。
  • 如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。
  • 如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。

追问:React 的请求应该放到哪里?为什么?

  • 对于异步请求,应该放到componentDidMount 中去操作,从生命周期函数执行顺序来看,除了componentDidMount 之外还有下面两种选择:
    • constructor 中可以放,但是不推荐,因为 constructor 主要用于初始化 state 和函数绑定,并不承载业务逻辑,而且随着类属性的流行,constructor 很少用
    • componentWillMount 已被标记废弃,因为在新的异步渲染下该方法会触发多次渲染,容易引发bug,不利于 React 的后期维护和更新
  • 所以React 的请求放在 componentDidMount 里是最好的选择
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/857441
推荐阅读
相关标签
  

闽ICP备14008679号