剖析React生命周期的运作机制
1、React生命周期
1.1、何为React生命周期?
当我们借助各类框架开展程序开发时,在框架启动、程序初始运行、页面之间相互交互、数据渲染到页面之上以及程序运行结束的整个过程里,存在一个闭环式的操作流程。在这个闭环的起始点与结束点之间的各个节点处,框架为我们提供了特定的函数,我们可以自行调用这些函数来执行相应操作,这便是生命周期。
简单来讲: 组件从创建、更新直至销毁的完整历程。
其意义体现在:有助于深入理解组件的运行模式、实现更为复杂的组件功能、剖析组件出错的缘由等。
注意: 在React中存在函数组件和类组件。类组件拥有生命周期函数,而函数组件则借助useEffect来达成生命周期函数的功能。
1.2、演变历程
生命周期的每一个阶段都会伴随着若干方法的调用,这些方法就是生命周期的钩子函数,它们的作用是为开发者在不同阶段对组件进行操作提供时机。
众多的生命周期钩子,归结起来实则只有三个过程:挂载、更新、卸载,挂载和卸载仅会执行一次,更新则会执行多次。
- 挂载:已然插入到真实的DOM中
- 渲染(更新):正在被重新进行渲染
- 卸载:已然从真实的DOM中移除
一个完整的React组件生命周期会依次调用如下钩子:
2、类组件生命周期
2.1、类组件是什么
类组件是运用ES6中的关键字class来进行定义的。类组件具备特定的生命周期方法,能够在不同阶段执行一些操作。类组件中必须实现render()方法来描述组件的界面,并且可以通过state属性来存储和管理组件的内部状态。
例如:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
state1: 'This is a React class.'
};
}
render() {
return (
<div>{state1}</div>
);
}
}
2.2、旧版生命周期
2.3、废弃与新增
原先(React v16.0之前)的生命周期在React v16推出Fiber之后就不再适配了,因为要是开启async rendering,在render函数之前的所有函数都有可能被执行多次。所以,除了shouldComponentUpdate之外,其他在render函数之前的所有函数(componentWillMount、componentWillReceiveProps、componentWillUpdate)都由getDerivedStateFromProps来替代。
以下生命周期钩子将会逐步被废弃,它们的共同特点是都带有will的钩子:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
引入了以下两个生命周期钩子:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
注意:
getDerivedStateFromProps前面需要加上static保留字,声明为静态方法,不然会被react忽略掉;getDerivedStateFromProps里面的this为undefined。
3、新版类组件生命周期
3.1、React的生命周期包含哪些?
- 挂载阶段(Mounting)
- 更新阶段(Updating)
- 卸载阶段(Unmounting)
- 错误处理阶段(Error Handling)
3.2、生命周期详细解析
在React中,组件的生命周期历经不同阶段,每个阶段都对应着相应的生命周期方法。以下是针对React 16版本之后的组件生命周期方法进行的详细剖析:
挂载阶段(Mounting):(当组件实例被创建并插入到DOM中时)
- constructor(props):组件的构造函数,在React组件挂载之前,也就是创建组件的时候被调用,用于初始化状态以及绑定事件处理方法。该方法只会执行一次。
- static getDerivedStateFromProps(props, state):这是一个静态方法,接收props和state两个参数。第一个参数是即将更新的props,第二个参数是上一个状态的state,可以通过比较props和state来设置一些限制条件,防止无用的state更新。每次接收到新的props之后都会返回一个对象作为新的state,如果返回null则表明不需要更新state。
- render():准备对组件的UI结构进行渲染。在此处创建虚拟dom、进行diff算法以及更新dom树等操作。
- componentDidMount():组件第一次渲染完成后,在组件挂载(插入到DOM树中)后立即被调用,通常用于执行一次性的操作,比如数据获取、订阅事件等。
更新阶段(Updating):(当组件的props或state发生变化时会触发更新)
- static getDerivedStateFromProps(props, state):在组件接收到新的props时被调用,用于根据新的props来更新state。
- shouldComponentUpdate(nextProps, nextState):在渲染执行之前被调用,首次渲染或者使用forceUpdate()时不会调用该方法。返回true就会更新dom(使用diff算法进行更新),返回false则能阻止更新(不调用render)
- render():重新对组件的UI结构进行渲染。
- getSnapshotBeforeUpdate(prevProps, prevState):在最近一次渲染输出(提交到DOM上)之前被调用,此时DOM树还没有改变,我们可以在这里获取DOM改变前的信息,例如更新前DOM的滚动位置。
- componentDidUpdate(prevProps, prevState, snapshot):第一个参数是上一次props的值,第二个参数是上一次state的值。在更新后立即被调用,首次渲染不会执行此方法。通常用于处理更新后的操作,比如数据同步、DOM操作等。
卸载阶段(Unmounting):(当组件从DOM中移除时)
- componentWillUnmount():在组件渲染之后、组件卸载及销毁之前直接被调用,只调用一次。在此方法中执行必要的清理操作,例如,清除timer,取消网络请求或者清除在componentDidMount()中创建的订阅等。
错误处理阶段(Error Handling):(当渲染过程、生命周期或者子组件的构造函数中抛出错误时)
- static getDerivedStateFromError(error):当子组件抛出错误时被调用,用于更新组件的state以渲染降级UI。
- componentDidCatch(error, info):用于捕获组件内部的JavaScript错误、网络请求失败等异常情况,并进行错误处理。
3.3、实例
3.3.1、挂载阶段
import React ,{Component} from 'react'
class Child extends Component{
// 初始化
constructor(props){
console.log('01构造函数')
super(props)
this.state={}
}
// 组件将要挂载时候触发的生命周期函数
componentWillMount(){
console.log('02组件将要挂载')
}
// 组件挂载完成时候触发的生命周期函数
componentDidMount(){
console.log('04组件挂载完成')
}
// 组件挂载时触发的生命周期函数
render(){
console.log('03数据渲染render')
return(
<div>Child组件</div>
)
}
}
export default Child
// 控制台打印结果顺序如下:
// 01构造函数
// 02组件将要挂载
// 03数据渲染render
// 04组件挂载完成
3.3.2、更新阶段
数据更新时第一步是shouldComponentUpdate来确认是否要更新数据,当这个函数返回true的时候才会进行更新,并且这个函数可以声明两个参数nextProps和nextState,nextProps是父组件传给子组件的值,nextState是数据更新之后的值,这两个值可以在这个函数中获取到。第二步当确认更新数据之后componentWillUpdate将要更新数据,第三步依旧是render,数据发生改变render重新进行了渲染。第四步是componentDidUpdate数据更新完成。
import React, { Component } from 'react'
class Child extends Component {
constructor(props) {
super(props)
this.state = {
msg: '我是一个msg数据'
}
}
//是否要更新数据,如果返回true才会更新数据
shouldComponentUpdate(nextProps, nextState) {
console.log('01是否要更新数据')
return true; //返回true,确认更新
}
//将要更新数据的时候触发的生命周期函数
componentWillUpdate() {
console.log('02组件将要更新')
}
//更新完成时触发的生命周期函数
componentDidUpdate() {
console.log('04组件更新完成')
}
//更新数据方法
setMsg() {
this.setState({
msg: '我是改变后的msg数据'
})
}
render() {
console.log('03数据渲染render')
return (
<div>
{this.state.msg}
<button onClick={() => this.setMsg()}>点我更新msg的数据</button>
</div>
)
}
}
export default Child
点击按钮更新数据,控制台打印结果顺序如下:
01是否要更新数据
02组件将要挂载
03数据渲染render
04组件更新完成
3.3.3、总结
这些生命周期方法帮助我们在组件创建、更新和销毁时执行特定的操作,确保应用程序的正常运行和良好的用户体验。
4、函数组件生命周期
只有类组件才有生命周期,函数组件不存在生命周期(类组件需要实例化,而函数组件不需要)。React v16.8.0推出了Hooks API,其中的一个API叫做useEffect可以解决相关问题。Hook基本完全替代了类组件的语法,后续的React项目就完全是函数式组件了。
4.1、函数组件是什么
函数组件是一种以声明式方式定义组件的形式,使用function关键字来定义一个新的函数。函数组件被设计成无状态的,仅仅以props作为输入并返回React元素作为输出。函数组件不需要实现类组件中的生命周期方法和state状态。
例如:
import React, { useState } from 'react'
import TestLifeCycle from './pages/testLifeCycle'
const App = () => {
const [show, setShow] = useState(true)
const unMountClick = () => {
setShow(!show)
}
return (
<div className="App">
{ show ? <TestLifeCycle/> : null }
<button onClick={unMountClick}>卸载</button>
</div>
)
}
export default App
4.2、模拟类组件生命周期钩子
useEffect是一个高阶函数,可以在一个组件中多次使用,相当于componentDidMount(组件挂载完成)、componentDidUpdate(组件更新完成)和componentWillUnmount(组件将要卸载之前)的组合。
useEffect方法可以传递两个参数:
第一个参为函数结构,是必填项;
第二个参数可为数组,是选填项。
所以函数组件可以分为三种:
第一种: 不带参数
useEffect(() => {
// todo...
})
// 不传第二个参数,则会在 state 的任意一个属性改变时都会触发该函数回调
初始化和组件重新渲染的时候执行。父组件重新渲染也会执行。
第二种: 带空数组 []
useEffect(() => {
// todo...
}, [])
// 第二个参数为空时只会在第一次渲染时执行
组件初始化(挂载)的时候执行,可以用于ajax数据请求。
第三种: 带具体参数的数组(可以多个参数)
useEffect(() => {
// todo...
}, [参数1,参数2,...])
其中一个值或多个发生改变时执行。
用useEffect代替生命周期:
1、模拟componentDidMount():
useEffect(() => {}, [])函数组件挂载时执行一次
2、模拟componentDidUpdate():
useEffect(() => {})只要有state变化,就执行函数
useEffect(() => {}, [state1, state2])state1或state2变化,就执行函数
3、模拟componentWillUnmount:
useEffect(() => { 挂载执行的函数体 return () => { 卸载执行的函数体 } })
总结:
例子:
import React, { useState, useRef, useEffect } from 'react'
export default function MyComponent() {
// 第一个参数是函数结构,第二个参数是空数组 ===> 表示ComponentDidMount 组件挂载完成
useEffect(() => {
console.log('组件挂载完成了1');
}, []);
// 一打开页面,组建完成挂载,自动执行
// 第一个是函数结构,不传递第二个参数 ===> 表示表示ComponentDidMount + ComponentDidUpdate
// 即:组件挂载完成以及组件状态更新
useEffect(() => {
console.log('组件挂载完成 & 组件更新完成');
})
// 组件挂载完成会执行一次,组件每次更新状态值也会执行一次
let [num, setNum] = useState(100);
let [isLogin, setisLogin] = useState(true);
// 第一个是函数结构,第二个是非空数组,数组中传入state的状态数据 ===> 当这个状态数据被更新时才执行回调函数
// 【注】数组的参数如果没有任何值的话,空数组,表示什么状态数据都不更新,回调函数不执行
useEffect(() => {
console.log('当islogin状态值发生改变时,组件挂载完成&组件更新完成')
}, [isLogin])
// 模拟ComponentwillUnmount
// 在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
// 通过函数里返回函数的方式
useEffect(() => {
console.log('任意属性变了');
return () => {
console.log('该组件要销毁了');
}
})
// return 中的内容会在页面卸载时执行,类似生命周期函数 componentWillUnmount。
// 比如在页面销毁的之前先清除定时器,避免不必要的性能开支。
useEffect(() => {
// 清除定时器
return () => {
clearInterval(timer1)
}
}, [])
注意:
useEffect方法的第一个参数的函数是一个同步函数,不接收async参数,(原因是如果设置了async,返回值就成了promise对象,非一个函数了)。
如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数。
useEffect(() => {
//函数声明
async function main() {
let res = await 100;
}
//函数调用
main();
})
文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/13675.html