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

news2024/11/18 2:49:23

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

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

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

constructor

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

import React from 'react';

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
     // do some thing
  }
  render() {
     return null
  }
}

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

import React from 'react'
class Counter extends React.Component {
   state = {
      count: 0,
   }

   // 类属性第三阶段提案
   handleClick = () => {
     // do some thing
   }
  render() {
     return null
  }
}

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

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

getDerivedStateFromProps

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

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

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

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

class Example extends React.Component {
   state = {
      isScrollingDown: false,
      lastRow: null
   };

   static getDerivedStateFromProps(props, state) {
     if (props.currentRow !== state.lastRow){
        return {
           isScrollingDown: props.currentRow > state.lastRow,
           lastRow: props.currentRow,
        }
     }
    // 返回 null 表示无需要更新state
    return null  
   }
}

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

两种反模式的使用方式:

  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 进行浅比较,用来判断是否重新触发更新。

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

UNSAFE_componentWillUpdate

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

render

同挂载阶段的表现一致

getSnapshotBeforeUpdate

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

官方案例:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }
 
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }
 
  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }
 
  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }

}

componentDidUpdate

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

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

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

卸载阶段

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

componentWillUnmount

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

职责梳理

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

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

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

函数组件

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

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

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

React.Component

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

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

React.PureComponent

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

错误边界

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

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // 同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
  }
}

但是渲染时的报错,只能通过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 里是最好的选择

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/72763.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMMI2.0配置管理工作及访谈学习笔记

1. 配置管理概述 1.1. 关于配置管理 配置管理是通过技术或其他手段对软件产品及其开发过程和生命周期进行控制、规范的一系列措施&#xff0c;通过配置标识、版本控制、版本管理、基线管理和配置审计来管理工作产品的完整性。 配置管理的主要目的是进行工作产品管理&#xf…

JavaScript -- 12. jQuery

文章目录jQuery1 jQuery简介2 使用jQuery2.1 下载jQuery2.2 引用jQuery3 jQuery的核心函数3.1 作为工具类使用3.2 作为函数使用3.2.1 将一个函数作为$的参数3.2.2 将选择器字符串作为参数3.2.3 将DOM对象作为参数3.2.4 将html代码作为参数4 jQuery对象4.1 获取DOM对象4.2 隐式迭…

Python——基础知识

前面主要讲解了javaEE的基础知识&#xff0c;基本上可以用servlet写一些简单的网页程序&#xff0c;后续的博客将围绕spring进行讲解&#xff0c;并且其中穿插一些python的知识 变量 命名 和其他的编程语言一样&#xff0c;java的变量命名需要遵循下面几个原则 由数字&…

世界杯竞猜项目Dapp-第二章(hardhat部署合约)

创建 hardhat 项目 # 创建 npm 空项目 npm init # 安装 npm install --save-dev hardhat2.11.1 # 创建工程 npx hardhat -> 选择高级ts项目运行测试 # 编译合约 npx hardhat compile # 单元测试 npx hardhat test添加合约 将 Worldcup.sol&#xff08;上节编写的合约&am…

【Flink】自定义keyBy的KeySelector

我们通常在写Flink程序的时候都会遇到keyBy,比如按照某条数据的某个字段进行分类计算,或者计算pv,还有需要用到Flink定时器的高级功能。 下面我们根据keyby的源码,看下keyBy的用法(本博客基于Flink1.13) 源码有5种方式定义keyBy, 但是其中的2种已经不用了,如果用户想…

PC的ARM的安全启动

其实还是很不习惯将PC和ARM联系起来&#xff0c;当然主要是因为我刚刚接触嵌入式没有多久&#xff0c;更别说服务器、PC端了。 脑子里固化了的X86。 于是这一篇学习一下&#xff0c;基于ARM的PC&#xff0c;是怎么利用这个ATF框架&#xff0c;进行安全启动的。 文章内容来自…

9个做好动效设计的好方法

动态效果意味着充满生命和兴奋&#xff0c;为静态事物增添生命。然而&#xff0c;在软件方面&#xff0c;动态效果不仅是为了获得乐趣&#xff0c;也是为了解决问题。 作为人类&#xff0c;我们习惯于看到世界在我们周围移动。如果任何软件有连贯的动画&#xff0c;它都会感到…

【Python游戏】Python基于第三方库pygame实现一个魂斗罗小游戏,毕业设计必备 | 附源码

前言 halo&#xff0c;包子们下午好 今天给打击整一个魂斗罗小游戏 很多小伙伴接触魂斗罗应该是在小时候的一个手柄游戏上面吧 我记得作为90后的我&#xff0c;玩这一款游戏是在小学的时候 废话不多说&#xff0c;直接上才艺 今天给大家直接安排 相关文件 关注小编&#xff…

30分钟了解linux操作系统内核总结

【推荐阅读】 概述Linux内核驱动之GPIO子系统API接口 一篇长文叙述Linux内核虚拟地址空间的基本概括 轻松学会linux下查看内存频率,内核函数,cpu频率 纯干货&#xff0c;linux内存管理——内存管理架构&#xff08;建议收藏&#xff09; Linux 内核性能优化的全景指南&#xff…

中学语文杂志中学语文杂志社中学语文编辑部2022年第30期目录

理论_视点《中学语文》投稿&#xff1a;cn7kantougao163.com 追求服务于学的“智慧语文”教育 陈维贤; 3-5 教学_阅读教学 高中古诗词群文阅读教学特点新探 张红娟; 6-8 导读需导思 领读方领悟 罗艳兰; 9-10 学习任务群背景下的群文阅读教学研究 丁雪云; 11-12…

守门员VS软件测试

虽然刚刚为世界杯贡献了点彩票钱&#xff0c;但我依然热爱着足球这项赛事。最近熬夜看球&#xff0c;突然发现我们软件开发团队里&#xff0c;守门员这个职位就像我们公司的那个测试倒霉蛋儿一样。 1、球队配比VS开发团队人员配比 △ 不管你的球队是443&#xff0c;还是552&am…

如何自定义SpringBoot中的starter,并且使用它

目录 1 简介 2 规范 2.1 命名 2.2 模块划分 3 示例 1 简介 SpringBoot中的starter是一种非常重要的机制&#xff0c;能够抛弃以前繁琐的配置&#xff0c;将其统一集成进starter&#xff0c;应用者只需要在maven中引入starter依赖&#xff0c;SpringBoot就自动扫描到要加载…

PyTorch笔记 - A ConvNet for the 2020s (ConvNeXt) 网络

欢迎关注我的CSDN:https://blog.csdn.net/caroline_wendy 本文地址:https://blog.csdn.net/caroline_wendy/article/details/128236899 Paper:A ConvNet for the 2020s,FAIR,Berkeley Code:https://github.com/facebookresearch/ConvNeXt 网络结构: stem layer:骨干层…

【k8s宝典】2022年12月份Kubernetes 认证管理员CKA轻松通过攻坚克难技巧

目标&#xff1a;攻克三大难点 提示&#xff1a;本人是12月近期通过的cka考试&#xff0c;所以总结的技巧是当前最新的&#xff0c;后面如果有稍许变化&#xff0c;这些技巧照样适用&#xff0c;题目请以实际为主 复制键不能用怎么办&#xff1f; 新版考试系统使用Ctrl c不…

【论文阅读笔记】Noise2Noise: Learning Image Restoration without Clean Data

官方代码地址&#xff1a;https://github.com/NVlabs/noise2noise 论文地址&#xff1a;https://arxiv.org/abs/1803.04189 第一次接触这方向&#xff0c;相当于翻译了。 摘要 作者通过机器学习将基础统计推理应用于信号重建上&#xff1a;学习将损坏的观察结果&#xff08;co…

商务与经济统计 | 描述统计学

一.表格法和图形法 条形图 是一种描述已在一个频数&#xff0c;相对频数或百分数频数分布中汇总的品质数据的图形方法 饼形图 是一种常用的描绘品质数据的相对频数分布的图形方法 直方图 横轴上表示所关心的变量&#xff0c;纵轴上表示频数&#xff0c;相对频数或百分数频数…

23软考备考已开始,网络工程师知识点速记~(5)

新一轮软考备考来啦~ 为了帮助大家提高备考效率&#xff0c;将2023上半年软考网络工程师知识点速记分享给大家&#xff0c;快来跟着一起打卡学习吧&#xff01; 进程的同步和互斥 计算机有了操作系统后性能大幅度提升&#xff0c;其根本原因就在于实现了进程的并发运行。多个…

接口多实现

一、什么是接口 接口是抽象类的延伸&#xff0c;可以将它看作是纯粹的对象类 二、接口模式的特性 &#xff08;1&#xff09;接口不可以被实例化。 &#xff08;2&#xff09;实现类必须实现接口的所有方法&#xff08;类似于抽象类和抽象方法&#xff09;。 &#xff08;3&…

基于百度飞桨PaddleOCR的图片文字识别

PaddleOCR项目源码&#xff1a;https://github.com/PaddlePaddle/PaddleOCR 飞桨开源文字识别模型套件PaddleOCR&#xff0c;目标是打造丰富、领先、实用的文本识别模型/工具库。最新开源的超轻量PP-OCRv3模型大小仅为16.2M。同时支持中英文识别&#xff1b;支持倾斜、竖排等多…

[附源码]Python计算机毕业设计Django-中国传统手工艺销售平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…