原文地址
每个组件都包含“生命周期方法”,我们可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。
生命周期图谱
React官方提出了三个特定阶段:挂载阶段、更新阶段、卸载阶段。
挂载阶段
constructor()
static getDerivedStateFormProps()
render()
componentDidMount()
constructor()
在React组件挂载之前,会调用它的constructor
(构造函数),是最先被执行的函数,在constructor
中通过调用super(props)
来获取外部传递过来的数据。通常,构造函数仅用于两种情况:
- 通过给
this.state
赋值对象来初始化内部state
- 为事件处理函数绑定实例(为自定义方法绑定
this
)
static getDerivedStateFromProps()
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null
则不更新任何内容。
简单来说这个生命周期函数的作用就是让我们可以将传入组件的props映射到state上。
一个简单的例子:
有一个App
组件,维护一个num
变量,定义一个函数可以给num
变量加一,绑定给一个按钮,并将变量num
传递给子组件Mount
。
function App() {
const [num, setNum] = useState(0)
const changeNum = () => {
setNum(v=>v+1)
}
return (
<div className="App">
<button onClick={changeNum}>外部改变</button>
<Mount num={num}/>
</div>
)
}
有一个Mount
组件,维护一个自身num
变量初始化为父组件num
值,该组件也有一个改变num
的函数绑定了一个按钮,并且展示props.num
和state.num
。
class Mount extends Component {
constructor(props) {
super(props)
this.state = {
num: props.num,
}
}
changeNum = () => {
this.setState({
num: this.state.num + 1
})
}
render() {
return (
<div>
<div>state: {this.state.num}</div>
<div>props: {this.props.num}</div>
<button onClick={this.changeNum}>内部改变</button>
</div>
)
}
}
现在当我们点击父组件按钮时,子组件的props.num
发生了变化,点击子组件按钮时子组件的state.num
也会变化。
现在我们需要实现一个当点击父组件按钮,子组件state.num
也要和父组件props.num
一起变化的效果,就可以用到getDerivedStateFromProps
生命周期函数。
只需要给子组件添加如下代码:
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.num !== prevState.num) {
return {
num: nextProps.num
}
}
return null
}
该生命周期函数接收两个参数,根据以上参数名就可见名知意。
值得一说的是,getDerivedStateFromProps
的执行时间是每次触发render
之前,所以如果添加以上函数,就会造成一个现象,即就是再点击子组件的按钮state.num
将无法更新。
因为setState()
也会导致getDerivedStateFromProps
执行,而此时setState
已经改变了state.num
,所以也就导致了生命周期函数中的if()
条件是成立的,结果就是state.num
又被重新赋值成了之前的props.num
。
要解决这一个’BUG’,其实只需要保存setState
执行之前的state.num
的值,然后让if()
中的判断不再是和state.num
比较,而是和state.pervNum
比较,如下:
constructor(props) {
super(props)
this.state = {
num: props.num,
prevNum: props.num
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.num !== prevState.prevNum) {
return {
num: nextProps.num,
prevNum: nextProps.num
}
}
return null
}
这样每当触发setState
时,就不会影响if()
的判断了,也就促使了子组件的setState
不受该生命周期的影响。
render()
render() 方法是 class 组件中唯一必须实现的方法。
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
- React元素
- 数组或fragments
- Portals
- 字符串或数值类型
- 布尔类型或null
render()
函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
componentDidMount()
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
简单来说就是在组件初始化渲染之后立刻调用一次,且在整个组件的生命中只调用一次。
这应该是最常用的生命周期函数之一,通常用来进行实例化网络请求、添加订阅、添加事件监听等等。
更新阶段
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
shouldComponentUpdate()
该函数接收两个参数:shouldComponentUpdate(nextProps, nextState)
该函数的返回值将直接影响到组件的重新渲染(默认情况下返回true,返回false时将告知React跳过更新),我们可以使用这两个参数和自写逻辑去控制组件的渲染。
所以该函数是在组件重新渲染即render()
被调用之前执行的。
值得一说的是,该函数独属于更新阶段,并且forceUpdate()
是不会调用该方法的。
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(pervProps, pervState)
在最近一次渲染输出(提交到 DOM 节点)之前调用。
在此方法中我们可以访问更新前的props
和state
,并且必须和componentDidUpdate()
一起使用,因为此方法的返回值将作为componentDidUpdate()
的第三个参数。(ps. 如果使用了该生命周期,则返回值不能为undefined,否则会报错)
这个生命周期执行的时机非常的微妙:
它是处于render()
之后,DOM更新之前。在此处获取的DOM状态依然是之前的DOM状态,所以此生命周期经常被用来做一些与DOM相关的操作(例子)。
componentDidUpdate()
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。
任何导致组件重新渲染的情况都会让该生命周期函数执行,例如:setState()
、forceUpdate()
、新的props
。
这里我们可获取三个参数componentDidUpdate(prevProps, prevState, snapshot)
利用前两个参数,我们可以对更新前后的props
和state
进行比较,以便做出响应,比如说某种条件下进行网络请求或不进行请求,或者进一步对state
做出更改。
值得一说的是,在这个函数中调用setState()
必须要在条件语句中,而不能直接暴露出来。
因为一旦直接暴露出来,setState()
的直接执行,又会导致该生命周期函数执行,进而陷入死循环,所以在此处使用setState()
一定要慎之又慎。
如果组件实现了 getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为 componentDidUpdate()
的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
卸载阶段
componentWillUnmount()
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
componentWillUnmount()
中不应调用 setState()
,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
注:本博客仅供个人学习,如有错误、侵权敬请指出。