React 组件生命周期

求上进的人,不要总想着靠谁,人都是自私的,自己才是最靠得住的人。

React 中生命周期划时代几个节点,React 16.2 之前处于老的生命周期,之后提出了新的生命周期。而函数式组件在 React 16.8 之前是没有状态和生命周期的,在 React 16.8 版本通过引入 Hooks 使得函数式组件也能有状态和生命周期了。

1. 初始化阶段

1.1 componentWillMount:

组件即将挂载,初始化数据作用,即 render 之前最后一次修改状态的机会。

react 复制代码
// 组件即将挂载
componentWillMount() {
  // 初始化数据作用
  console.log("componentWillMount")
}

/* 在 16.2 之后版本使用会出现以下警告 ⚠️⚠️⚠️
  react-dom.development.js:86 Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.

  * Move code with side effects to componentDidMount, and set initial state in the constructor.
  * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.

  Please update the following components: App
  */

// 组件即将挂载 - 强制去掉警告,UNSAFE 提示开发者这是一个不安全的生命周期方法。
UNSAFE_componentWillMount() {
  // 初始化数据作用
  console.log("componentWillMount")
}

componentWillMount 在16.2 之后官方不推荐使用了,这是因为 16.2 的时候 React 发生了一个改变,推出了几个新的生命周期,老的生命周期方法被替代掉了,不推荐使用。 那么为什么 React 要推出新的生命周期呢?

在 React 16.2 中通过对 diff 算法的更新,更加优化它的性能。提出了一个 Fiber 技术(纤维、分片、切片,比线程更小的一种概念)。因为我们传统的 React 它在创建、更新状态之后会创建新的虚拟 Dom 树,会对比老的虚拟 Dom 树,这个过程是同步的,如果数据量比较小还好。如果这个数据量非常多的情况下即组件非常多的情况下(例如:几百个组件),这个时候更新操作,会导致我们浏览器假死、卡顿,这个时候点什么都没有反应,因为它忙着新老虚拟 Dom 的对比,就是它在对比两个超级大的对象,里面包含了很多小对象,这时浏览器无法处理其它事件,所以导致卡顿影响体验。

所以这个东西就是一个边缘化的问题,你的组件达到这样一个程度,它真的会出现假死的情况。所以 React Fiber 技术就是来优化了虚拟 Dom 的 diff 算法,它把创建 Dom 和++组件渲染整个过程++拆分成了无数个小的分片任务来执行。可以认为这个任务无比的碎片化,这个时候如果有优先级较高的任务就先执行优先级较高的任务。当优先级较低的任务在执行时候突然来了优先级较高的任务,这个时候会打断正在执行的低优先级任务,先执行优先级高的任务。所谓的低优先级任务就是 componentWillMount 中去找哪些节点将要去挂载到页面中,而高优先级任务就是 render 正在渲染,didMount 挂载完成。这个时候我们低优先级的任务(找出那些需要更新的 Dom)这个过程是会被打断的,而我们更新 Dom 这个过程是不能被打断的,只能一鼓作气做完的,而 willMount 很不幸它是处在这个要找出来那些 Dom 是需要被更新的。所以这个过程是可以被打断的,所以可以认为 willMount 在忙着找出那些状态需要更新。因为接下来在 render 中就要开始更新了,didMount 就更新完成了。这个时候 willMount 找是处于低优先级的,而这个时候 render 正在更新,因为碎片化任务,他可能还不是同步的。即某个组件可能处在在找那个状态需要更新,那个 Dom 需要更新,而那边组件已经到了 render 渲染部分了,这个时候就吧低优先级的任务给砍掉了。砍掉怎么办,会保存吗?不会。只能下次再来一遍,再来找那个节点需要更新。所以这个生命周期就可能会触发多次这样一个问题(失去了唯一性),所以这是一个有隐患的生命周期方法,所以这里不推荐使用。

1.2 render

组件正式挂载渲染,只能访问 this.props 和 this.state,不允许修改状态和 Dom 输出。

1.3 componentDidMount

组件挂载完成,成功 render 并渲染完成真实 DOM 之后触发,可以访问、修改 Dom。

react 复制代码
componentDidMount() {
  // 数据请求axios
  // 订阅函数调用
  // setInterval
  // 基于创建的完的dom进行初始化时候,例如 BetterScroll 使用
  console.log("componentDidMount")
}

2. 运行中阶段

2.1 componentWillUpdate

组件即将更新,不能修改属性和状态,会造成死循环。非安全被弃用,同 componentWillMount。

react 复制代码
// 组件即将更新
componentWillUpdate() {
  console.log("componentWillUpdate")
}

/* 在 16.2 之后版本使用会出现以下警告 ⚠️⚠️⚠️
  react-dom.development.js:86 Warning: componentWillUpdate has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.

* Move data fetching code or side effects to componentDidUpdate.
* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.

Please update the following components: App
  */

// 组件即将更新 - 强制去掉警告,UNSAFE 提示开发者这是一个不安全的生命周期方法。
 UNSAFE_componentWillUpdate() {
  console.log("componentWillUpdate")
}

2.2 render

组件正式挂载渲染,只能访问 this.props 和 this.state,不允许修改状态和 Dom 输出。

2.3 componentDidUpdate

组件更新完成,成功 render 并渲染完成真实 DOM 之后触发,可以访问、修改 Dom。

react 复制代码
// 组件更新完成 - 接收两个行参,老的属性、老的状态
componentDidUpdate(prevProps, prevState) {
  console.log(prevState)
  console.log("componentDidUpdate")
}

2.4 shouldComponentUpdate

scu 控制组件是否应该更新,即是否执行 render 函数。

react 复制代码
// 组件是否应该更新?- 接受两个行参,新的属性、新的状态
shouldComponentUpdate(nextProps, nextState) {
  if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {
    return true
  } else {
    return false
  }
}

2.5 componentWillReceiveProps

父组件修改属性触发,非安全被弃用,同 componentWillMount。处在 diff 中第一个阶段,找到哪些需要更新的 Dom。例如:如果父组件连续多次修改属性传递将触发多次 ajax 请求等。

react 复制代码
// 父组件修改属性触发,应用在子组件中才有意义
componentWillReceiveProps(nextProps) {
  // 最先获得父组件传来的属性,可以利用属性进行ajax或者逻辑处理
  // 把属性转换为孩子的自己的状态等
}

3. 销毁阶段

3.1 componentWillUnmount

在删除组件之前进行清理操作,比如计时器和事件监听器。

4. 新生命周期

4.1 getDerivedStateFromProps

getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的 state,返回 null 则说明不需要在这里更新 state。

在初始化中代替 componentWillMount 。在父传子中能代替 componentWillReceiveProps。

这里不能做异步操作,因为这里 return 是立即返回的。

react 复制代码
static getDerivedStateFromProps(nextProps, nextState) {
  console.log("getDerivedStateFromProps")
  return {

  }
}

4.2 getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 取代了 componentDidUpdate,触发时间为 update 发生的时候,在 render之后 Dom 渲染之前返回一个值,作为 componentDidUpdate 的第三个参数。

react 复制代码
import React, { Component } from 'react'

export default class App extends Component {
  state = {

  }

  // getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的 state,返回 null 则说明不需要在这里更新 state
  // 这里不能做异步操作,因为这里 return 是立即返回的
  static getDerivedStateFromProps(nextProps, nextState) {
    console.log("getDerivedStateFromProps")
    return {

    }
  }

  getSnapshotBeforeUpdate() {
    return 100
  }

  componentDidUpdate(prevProps, prevState, value) {
    console.log(value)
  } 

  render() {
    return (
      <div>
        <button onClick={()=>{
          this.setState({

          })
        }}>修改</button>
      </div>
    )
  }
}

5. React 中性能优化

5.1 shouldComponentUpdate

React 手动优化,控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下,需要进行优化。

5.2 PureComponent

React 自动优化,PureComponent 会帮你比较新 props 跟旧的 props,新的 state 和老的 state(值相等,或者对象含有相同的属性、且属性值相等),决定 shouldComponentUpdate 返回 true 或者 false,从而决定要不要呼叫 render function。

注意:如果你的 state 或 props『永远都会变』,那 PureComponent 并不会比较快,因为 shallowEqual 也需要花时间,比如倒计时功能,这就不适合使用 Pure Component 了。

5.3 缓存技术

React.Component 是使用 ES6 classes 方式定义 React 组件的基类:

react 复制代码
class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

PureComponent 和 memo 仅作为性能优化的方式而存在。但请不要依赖它来"阻止"渲染,因为这会产生 bug。PureComponnet 和 memo 都是通过对 props 值的浅比较来决定该组件是否需要更新的。

2.1 PureComponent (类组件)

React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 props 和 state 的方式来实现了该函数。

如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

2.2 memo(函数式组件)

函数组件缓存 memo,为啥起 memo 这个名字?在计算机领城,记记化是一种主要用来提高计算机程序速度的优化技术方案。它将开销较大的函数调用的返回结果存储起来,当同样的输入再次发生时,则这回缓存好的数据,以此提升运算效率。

React.memo 为高阶组件。它与 React.PureComponent 非常相似,但只适用于函数组件,而不适用 class 组件。

react 复制代码
const MyComponent = function MyComponent(props) {
  /* 使用 props 渲染 */
};
export default React.memo(MyComponent)

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果,即组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会走一遍渲染流程。但是 React.memo(),我们可以仅仅让某些组件进行渲染。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

react 复制代码
function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  
}
export default React.memo(MyComponent, areEqual);

示例:

react 复制代码
// 子组件代码:
import React, { memo } from 'react';
const Child = ()=>{
	console.log("2. 子组件渲染了")
	return (<div>子组件</div>)
}
export default Child

// 父组件代码:
import React, { memo } from 'react';
import Child from './Child.jsx'
const Father = ()=>{
	const [name,setName]=React.useState('');
	console.log("1. 父组件渲染了")
	return (<div>
		/* 在input框中输入内容,会走setName导致App组件重新渲染,但是子组件Child也会进行渲染。 */
		父组件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} />
		<Child />
	</div>)
}
react 复制代码
// 子组件代码:
import React, { memo } from 'react';
const Child = ()=>{
	console.log("2. 子组件渲染了")
	return (<div>子组件</div>)
}
export default memo(Child)
// 父组件代码:
import React, { memo } from 'react';
import Child from './Child.jsx'
const Father = ()=>{
	const [name,setName]=React.useState('');
	console.log("1. 父组件渲染了")
	return (<div>
		/* 解决:子组件使用memo包起来 */
		父组件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} />
		<Child />
	</div>)
}
相关推荐
小白学前端6663 天前
React Router 深入指南:从入门到进阶
前端·react.js·react
远洋录4 天前
React性能优化实战:从理论到落地的最佳实践
前端·人工智能·react
高山我梦口香糖5 天前
[react 3种方法] 获取ant组件ref用ts如何定义?
typescript·react
一叶茶7 天前
前端生成docx文档、excel表格、图片、pdf文件
前端·javascript·react
走,板砖去10 天前
大文件传输与断点续传实现(极简Demo: React+Node.js)
node.js·react
SRestia10 天前
Django+React---从0搭建一个听音乐+聊天室的网站
websocket·django·react
远洋录10 天前
前端部署实战:从人工发布到全自动化流程
前端·人工智能·react
Rudon滨海渔村12 天前
React-antd组件库 - 让Menu子菜单项水平排列 - 下拉菜单排序 - 自定义子菜单展示方式
前端·react.js·前端框架·react
远洋录14 天前
前端单元测试实战:从零开始构建可靠的测试体系
前端·人工智能·react
Krorainas14 天前
将PDF流使用 canvas 绘制然后转为图片展示在页面上(二)
前端·javascript·pdf·react