React在17.0.1版本修改了组件的生命周期,新的生命周期和旧的生命周期改变不大,新的生命周期增加了两个新的钩子函数,有三个钩子函数过时了。
一,旧生命周期
组件的生命周期可以分为三个阶段:初始化阶段,更新阶段,卸载阶段。
初始化阶段:依次触发。
constructor() --->
componentWillMount() --->
render() --->
componentDidMount()
更新阶段:有三种更新的方式,不同的方式从不同的钩子函数依次向下触发函数。
(1),第一种,也是更新阶段的整个阶段,由父组件重新render触发。
父组件render --->
componentWillReceiveProps() --->
shouldComponentUpdate() --->
componentWillUpdate --->
render() --->
componentDidUpdate()
(2),第二种,修改状态后触发(执行setState())。
执行setState() --->
shouldComponentUpdate() --->
componentWillUpdate --->
render() --->
componentDidUpdate()
(3),第三种,强制更新,执行forceUpdate()。
执行forceUpdate() --->
componentWillUpdate --->
render() --->
componentDidUpdate()
卸载阶段:由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount()
初始化阶段
<script type="text/babel">
// 创建组件
class Sum extends React.Component{
constructor(props) {
console.log('constructor')
super(props)
// 初始化状态
this.state = {count: 0}
}
// 增加按钮
add = () => {
// 获取原状态
const {count} =this.state
// 更新状态
this.setState({count: count+1})
}
// 组件将要挂载
componentWillMount() {
console.log('componentWillMount')
}
// 组件挂载完毕
componentDidMount() {
console.log('componentDidMount')
}
//render调用的时机:初始化渲染,状态更新之后
render() {
console.log('render')
const {count} = this.state
return(
<div>
<h2>{count}</h2>
<button onClick={this.add}>+1</button>
</div>
)
}
}
ReactDOM.render(<Sum />, document.getElementById('test'))
</script>
在初始化的阶段,常用的钩子函数是componentDidMount(),组件挂载完毕,例如,页面显示之后有一个计时的功能。
页面刚挂载好页面上的数字每隔一秒加1,定时器写到componentDidMount()中:
<script type="text/babel">
// 创建组件
class Sum extends React.Component{
state = {count:0}
unmount = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕只调用一次
componentDidMount() {
// 设置一个定时器,每隔一秒加一
this.timer = setInterval(()=>{
let {count} = this.state
count += 1
this.setState({count})
},1000);
}
// 组件将要卸载的时候
componentWillUnmount() {
// 清空计时器
clearInterval(this.timer)
}
//render调用的时机:初始化渲染,状态更新之后
render() {
return(
<div>
<h2>{this.state.count}</h2>
<button onClick={this.unmount}>卸载组件</button>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Sum/>, document.getElementById('test'))
</script>
更新阶段:
第一种中提到了父组件,这种方法需要两个组件,一个子组件,一个父组件。
首先创建两个组件,A组件,B组件,计算结果在A组件中进行,然后将计算好的结果传到B组件中,传输数据用props传,显示计算的结果在B组件中显示。A组件中将结果存到状态state中,更新状态必定会更新render,所以构成了由父组件重新render的条件。
<script type="text/babel">
//A组件
class Acomponent extends React.Component{
state = {count: 0}
add = () => {
this.setState({count: this.state.count+1})
}
render() {
return(
<div>
<div>A组件</div>
<button onClick={this.add}>+1</button>
<Bcomponent count={this.state.count}/>
</div>
)
}
}
//B组件
class Bcomponent extends React.Component{
componentWillReceiveProps() {
console.log('componentWillReceiveProps')
}
// 控制组件更新 如果不写会有一个默认值为真
// 写了必须有一个返回值
shouldComponentUpdate() {
console.log('shouldComponentUpdate')
return true
}
// 组件将要更新
componentWillUpdate() {
console.log('componentWillUpdate')
}
// 组件更新完毕
componentDidUpdate() {
console.log('componentDidUpdate')
}
render() {
console.log('render')
return(
<div>B组件{this.props.count}</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Acomponent />, document.getElementById('test'))
</script>
只有第一种更新情况才会触发 componentWillReceiveProps() 钩子函数。
shouldComponentUpdate()这个钩子函数的作用是可以控制组件的更新,返回一个布尔值,如果返回为true,会继续向下执行。
如果返回为false,组件就不会更新,之后的钩子函数也不会执行。
如果在代码中没有写shouldComponentUpdate()这个钩子函数,React会给一个默认值,默认值为true,如果代码中写了这个钩子函数,就必须写返回值。不写返回值会报错。
第二种方法是更新状态,更新状态会重新加载组件。
后面的内容不多,所以修改状态更新,强制更新和卸载组件的代码就写到一个代码里了。
<script type="text/babel">
// 创建组件
class Sum extends React.Component{
state = {count:0}
// 按钮的回调
add = () => {
// 获取原状态
const {count} =this.state
// 更新状态
this.setState({count: count+1})
}
// 卸载组件
unmount = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新
force = () => {
this.forceUpdate();
}
// 组件将要卸载
componentWillUnmount() {
console.log('componentWillUnmount')
}
// 控制组件更新 如果不写会有一个默认值为真
// 写了必须有一个返回值
shouldComponentUpdate() {
console.log('shouldComponentUpdate')
return true
}
// 组件将要更新
componentWillUpdate() {
console.log('componentWillUpdate')
}
// 组件更新完毕
componentDidUpdate() {
console.log('componentDidUpdate')
}
//render调用的时机:初始化渲染,状态更新之后
render() {
console.log('render')
const {count} = this.state
return(
<div>
<h2>{count}</h2>
<button onClick={this.add}>+1</button>
<button onClick={this.unmount}>卸载组件</button>
<button onClick={this.force}>不改变状态,强制更新</button>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Sum />, document.getElementById('test'))
</script>
第一个+1的按钮就是更新状态的按钮,将显示的数字存到状态中。依次会触发
可以通过控制 shouldComponentUpdate() 的返回值来控制是否更新组件。
第三种方法是强制更新组件,通过调用forceUpdate()函数。
因为是强制更新,必定更新,所以不用通过shouldComponentUpdate() 来控制。
在写代码的过程中,可以更新状态不更新组件,可以不更新状态强制更新组件,根据自己需要的场景选择。
卸载阶段
卸载阶段的话只有一个钩子函数,componentWillUnmount() 组件将要卸载,由React.unmountComponentAtNode() 触发卸载组件。
在这个阶段会做一些收尾性的工作,例如,前面的那个定时器的例子,在卸载时要把定时器在这个钩子函数中清除,不然会报错。
二,新生命周期
在了解新生命周期之前,我们要知道React为什么要修改生命周期,官网中的内容:
上面三个生命周期方法经常被误解和滥用,React团队要实现异步渲染,而在异步渲染中,它们潜在的误用问题可能更大,在React未来版本中可能出现bug。
就是说React团队在为之后React版本的更新提前铺路,把可能出现的问题提前解决一下。
引入了两个新的生命周期,静态的 getDerivedStateFromProps和 getSnapshotBeforeUpdate.
getDerivedStateFromProps() 强调它是静态的,所以在使用时,前面要加上static。
<script type="text/babel">
// 创建组件
class Count extends React.Component{
state = {count: 0 }
// 按钮的回调
add = () => {
// 获取原状态
const {count} =this.state
// 更新状态
this.setState({count: count+1})
}
// 从props得到一个派生的state
// 如果state的值在任何时候取决于props,可以使用
static getDerivedStateFromProps(props, state) {
console.log('getDeriveStateFromProps', props, state)
return null
// return props
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate', prevProps, prevState)
return 'React'
}
// 组件更新完毕
componentDidUpdate(prevProps, prevState, snapshotValue) {
console.log('componentDidUpdate', prevProps, prevState, snapshotValue)
}
//render调用的时机:初始化渲染,状态更新之后
render() {
console.log('render')
const {count} = this.state
return(
<div>
<h2>{count}</h2>
<button onClick={this.add}>+1</button>
{/*<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不改变状态,强制更新</button>*/}
</div>
)
}
}
ReactDOM.render(<Count count={2}/>, document.getElementById('test'))
</script>
它可以返回一个对象来更新state,或者返回null来表示新的props,不需要任何state的更新。但使用getDerivedStateFromProps方法会增加组件的复杂性,经常会导致bug,所以使用到这个钩子函数的情况极其少,能不用就不用。
另一个新的生命周期是getSnapshotBeforeUpdate,这个生命周期的返回值将作为第三个参数传递给componentDidUpdate.
图是上面代码的运行结果。
上面两行是组件初始化时触发的,下面四行时组件更新时触发的,getDerivedStateFromProps可以拿到当前组件的props和state的值,一直都是当前的值,而getSnapshotBeforeUpdate它拿的是之前的值,是组件还没有更新时候的值。
当组件更新完毕后,是更新之后的组件,更新之前的组件中的信息就没有了,之前组件的信息可以由getSnapshotBeforeUpdate返回并传给componentDidUpdate,能存储之前组件的信息。什么都能存储,例如,保留滚动位置,组件的大小数据等。在一些情况下还是非常有用的。
总结
不管是旧生命周期还是新生命周期,常用的钩子函数有三个
componentDidMount(),初始化的工作;
componentDidUpdate(),更新的工作;
componentWillUnmount(),收尾的工作。
这三个钩子函数在旧生命周期或新生命周期中都没有改变。
三,函数式组件中使用生命周期钩子函数
Hook是React16.8.0版本增加的新特性,新语法,可以在函数组件中使用state,refs以及其他的React特性(生命周期钩子函数)。
Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
import React from "react"
import ReactDOM from "react-dom"
function Component() {
const [count, setCount] = React.useState(0)
function add() {
setCount((count)=>{return count+1})
}
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
//相当于componentDidMount()
// React.useEffect(()=>{
// console.log('初始化')
// },[]) //括号中什么都不加,只在页面刚挂载时调用
//相当于componentDidUpdate()
// React.useEffect(()=>{
// console.log('初始化并更新')
// },[count]) //括号中加入状态,当状态改变时更新
//相当于componentWillUnmount()
React.useEffect(()=>{
console.log('初始化')
return ()=>{
console.log('卸载组件')
}
},[])
return (
<div>
<h2>{count}</h2>
<button onClick={add}>+1</button>
<button onClick={unmount}>卸载</button>
</div>
)
}
export default Component
(1)语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(2)可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()