目录
我们在了解组件的生命周期前,我们先做如下理解:
1.组件对像从创建到死亡它会经历特定阶段。
2.Ract组件对象包含一系列勾子函数(生命周期回调函数),在特定的时刻调用。
3.我们在定义组件时,在特定的生命周期回调函数中,做特定的工作。
React中类式组件的生命周期包括旧版本的旧生命周期,也有新版本的新生命周期,本节将从新、旧两种生命周期做详细讲解。
-
生命周期(旧)
React中旧版本的生命周期如下图所示
其中主要有三个阶段:
- 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor() =====> 类式组件构造函数,创造类实例化对象时触发
- componentWillMount() =====> 组件将要挂载(渲染)到页面上时触发
- render() =====> 组件挂载(渲染)到页面上时触发
- componentDidMount() =====> 组件完成挂载(渲染)到页面上时触发 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段: 由组件内部this.setSate() / this.forceUpdate() 或父组件render触发;其中由父组件render触发时,子组件需要接收新的props才会触发componentWillReceiveProps(),也就是说当子组件第一次接收到props时不会触发,只有第二次才可以
- shouldComponentUpdate() =====>组件是否更新的钩子,当 setState()得到新的state后,根据此钩子返回的布尔值选择是否急继续更新(true:允许更新 false:禁止更新,state值也不变)
- componentWillUpdate() ====>组件将要更新时触发;这里需要知道 forceUpdate() 强制更新方法,无论shouldComponentUpdate() 返回什么布尔值,当我们使用forceUpdate()后,可以在不更改state值的前提下更新页面
- render() =====> 组件更新时重新挂载(渲染)到页面上时触发
- componentDidUpdate() =====> 组件完成更新的挂载后触发
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() =====> 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
我们通过如下代码进行演示(旧)生命周期:
- 我们首先定义一个父组件Father
class Father extends React.Component{
constructor(prop){
console.log("Father --- constructor Father组件构造函数初始化完成")
super(prop)
this.state = {value:0}
}
//更新父组件响应事件
updateFather = ()=>{
let {value} = this.state
this.setState({value:value+1})
}
//销毁父组件响应事件
destroyeFather = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
console.log("Father --- Father组件已经卸载")
}
//组件将要挂载
componentWillMount(){
console.log("Father --- componentWillmount Father组件将要挂载")
}
render(){
console.log("Father --- render 渲染挂载组件")
return (
<div>
<h1>我是Father组件,我已经更新了 {this.state.value} 了</h1>
<button onClick={this.updateFather}>点击更新父组件</button>
<button onClick={this.destroyeFather}>点击销毁父组件</button>
<Son name="我是子组件"/>
</div>
)
}
//组件挂载成功
componentDidMount(){
console.log("Father --- componentDidMount Father组件挂载成功")
}
//组件将要卸载
componentWillUnmount(){
console.log("Father --- componentWillUnmount Father组件将要卸载")
}
//组件是否更新
shouldComponentUpdate(){
console.log("Father --- shouldComponentUpdate 判断Father组件更新state后是否更新界面")
return true
// return false 此函数返回值为false时,界面不会更新
}
//组件将要更新
componentWillUpdate(){
console.log("Father --- componentWillUpdate Father组件将要更新")
}
//组件已经更新
componentDidUpdate(){
console.log("Father --- componentDidUpdate Father组件已经更新")
}
}
-
然后定义子组件Son
class Son extends React.Component{
componentWillReceiveProps(){
console.log("Son --- componentWillReceiveProps Father组件render时第二次接收到prop")
}
render(){
return (
<div>
<h2>我是子组件</h2>
</div>
)
}
}
- 最后在已有的HTML节点中渲染组件
//老版本虚拟DOM渲染节点
ReactDOM.render(<Father/>,document.getElementById("test"))
//新版本虚拟DOM渲染节点
const root = ReactDom.createRoot(document.getElementById("test"))
root.render(<Father/>)
1.我们首先得到如下图界初始界面:
其中输出为:
Father --- constructor Father组件构造函数初始化完成
Father --- componentWillmount Father组件将要挂载
Father --- render 渲染挂载组件
Father --- componentDidMount Father组件挂载成功
2.当我们点击更新组件时,这里父组件Father会第二次向子组件Son传入Prop参数,此时会调用子组件Son中的componentWillReceiveProps()方法:
其中输出为:
Father --- shouldComponentUpdate 判断Father组件更新state后是否更新界面
Father --- componentWillUpdate Father组件将要更新
Father --- render 渲染挂载组件
Son --- componentWillReceiveProps Father组件render时第二次接收到prop
Father --- componentDidUpdate Father组件已经更新
3.当我们点击销毁按钮后,组件从页面上消失,同时输出为:
Father --- componentWillUnmount Father组件将要卸载
Father --- Father组件已经卸载
-
生命周期(新)
我们查阅React官方文档发现新版本的React中对于组件的生命周期弃用了三个钩子:
componentWillMount()
componentWillUpdate()
componentWillReceiveProps()
因为React团队一直致力于异步渲染,而上述三个生命周期的代码在 React 的未来版本中更有可能出现 bug,尤其是在启用异步渲染之后,所以在后续版本中要使用上述三个生命周期需要在钩子前边加上“UNSAFE_” 前缀。因此新的生命周期实现如图:
新的生命周期中新增了两个钩子:getDerivedStateFromProps() getSnapshotBeforeUpdate()
- getDerivedStateFromProps()
此钩子不由实例调用,因此声明方式为:
static getDerivedStateFromProps(props,state)
getDerivedStateFromProps接收两个参数,且会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个state对象或者null:如果返回null则不影响后续渲染更新;如果返回state对象,则后续对于state的修改就会失效,state值会一直保持不变,即这里如果返回参数中的组件三大属性之一的props,就会将props当作派生state使用,且派生state的值不会由于setState()方法更新,即state的值在任何时候都取决于props。例如:
class MyComponent extends React.Component{
state = {nums : 0}
//......
//此处省略其他钩子
//......
static getDerivedStateFromProps(props,state)
{
//这里返回的类似于state的对象
//如果其中包含的属性与真实state一样,即nums和nums,则通过setState()对nums的更新再也无效
return {nums:999}
//返回的来自props的派生state
//如果其中包含的属性与真实state一样,即nums和nums,则通过setState()对nums的更新再也无效
return props
//返回null对state的更新无影响
return null
}
//......
//此处省略其他钩子
//......
}
//这里传入的props在getDerivedStateFromProps返回后会得到来自props的派生state
ReactDOM.render(<MyComponent/ nums={999}>,document.getElementById("test"))
派生状态会导致代码的冗余,增加组件的复杂性,故不要轻易使用该钩子。
- getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(preProps,preState)
此钩子接收两个参数:preProps(React更新前上一次的Props)、preState(React更新前上一次的State)。
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到DOM节点)之前,即更新完成挂载前调用。它使得组件能在发生更改之前从DOM中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数snapshotValue传给componentDidUpdate(preProps,preState,snapshotValue)。
该钩子的使用案例如下所示:
假设我们在如下所示改变滚动条位置时,新添加的标签会将我们滚动查看的标签不断挤下去
而我们要达到如下效果
此时我们就要使用getSnapshotBeforeUpdate(),在更新成功前,我们保存未更新前的scrollHeight,并将其返回到componentDidUpdate()中,从而得到因为元素更新挤下去的相对位移。代码实现如下:
<style>
.news{
background-color:antiquewhite;
overflow: auto;
width: 150px;
height: 140px;
}
.val{
height: 20px;
color: black;
}
</style>
class Scroll extends React.Component{
state={arrayNews:[]} //初始化新闻条目
componentDidMount(){
//不断添加标签到页面上
setInterval(()=>{
const {arrayNews} = this.state
const addNews = `我是第${arrayNews.length+1}条新闻`
this.setState({arrayNews:[addNews,...arrayNews]})
},1000)
}
render(){
return(
<div className="news" ref={(c) => this.news = c}>
{this.state.arrayNews.map((val,idx)=>
{
return (
<div className="val" key={idx}>{val}</div>)
}
)
}
</div>
)
}
//在更新成功前,我们保存未更新前的scrollHeight,并将其返回到componentDidUpdate()中
getSnapshotBeforeUpdate()
{
return this.news.scrollHeight
}
//更新成功后,我们将当前的scrollHeight减去getSnapshotBeforeUpdate()返回的scrollHeight,即得到因为元素更新挤下去的相对位移
componentDidUpdate(preProps,preState,snapshot)
{
//更新当前scrollTop,保持当前可是页面不会因为元素增加而被滑走
this.news.scrollTop += this.news.scrollHeight - snapshot
}
}
//老版本虚拟DOM渲染节点
ReactDOM.render(<Scrool/>,document.getElementById("test"))
//新版本虚拟DOM渲染节点
const root = ReactDom.createRoot(document.getElementById("test"))
root.render(<Scrool/>)
我们对新的生命周期进行总结:
- 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor() =====> 类式组件构造函数,创造类实例化对象时触发
2. getDerivedStateFromProps() =====> 返回null或者获得派生state,这里如果返回state对象,后续state对象将不能被更改
3. render() =====> 组件挂载(渲染)到页面上时触发
4. componentDidMount() =====> 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps () =====> 返回null或者获得派生state,这里如果返回state对象,后续state对象将不能被更改
2. shouldComponentUpdate() =====>组件是否更新的钩子,当 setState()得到新的state后,根据此钩子返回的布尔值选择是否急继续更新(true:允许更新 false:禁止更新,state值也不变)
3. render() =====> 组件更新时重新挂载(渲染)到页面上时触发
4. getSnapshotBeforeUpdate() =====> 在组件更新完成前,得到一些重要的值返回给componentDidUpdate()
5. componentDidUpdate() ====> 组件完成更新的挂载后触发
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() =====> 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息