1 新版生命周期
React17.x生命周期如下图1-1所示:
React组件的生命周期可以分为三个主要阶段:挂载阶段,更新阶段和卸载阶段。每个阶段都有不同的生命周期方法可供使用。
- 挂载阶段(Mounting)
在挂载阶段,组件将被插入到DOM中。以下是挂载阶段的生命周期方法:
- constructor():组件被创建时调用,初始化组件的状态和绑定方法。
- static getDerivedStateFromProps():在组件挂载之前和更新时调用,可以根据新的props计算state。
- render():在组件挂载之前和更新时调用,返回虚拟DOM,用于渲染到页面。
- componentDidMount():在组件挂载后调用,可以执行DOM操作或发起网络请求等操作。
- 更新阶段(Updating)
在更新阶段,组件的props或state被修改,导致重新渲染。以下是更新阶段的生命周期方法:
- static getDerivedStateFromProps():在组件更新时调用,可以根据新的props计算state。
- shouldComponentUpdate():在组件更新时调用,决定是否需要重新渲染组件,可以优化性能。
- render():在组件更新时调用,返回虚拟DOM,用于渲染到页面。
- getSnapshotBeforeUpdate():在组件更新之前调用,可以获取更新前的DOM状态。
- componentDidUpdate():在组件更新后调用,可以执行DOM操作或发起网络请求等操作。
- 卸载阶段(Unmounting)
在卸载阶段,组件将从DOM中移除。以下是卸载阶段的生命周期方法:
- componentWillUnmount():在组件卸载前调用,可以执行清理操作,如取消网络请求或定时器等。
还有一些其他的生命周期方法,如错误处理的 componentDidCatch() 和 getDerivedStateFromError() 方法。这些方法可以帮助开发者更好地管理组件的生命周期和错误处理。
2 与旧版对比
React 17.x版本没有引入新的生命周期方法,但是对生命周期方法的行为进行了一些改变。
- 生命周期方法的弃用
React 17.x废弃了三个生命周期方法:componentWillMount()、componentWillReceiveProps()、componentWillUpdate()。这些方法在React 16.x版本中仍然可用,但在React 17.x版本中不推荐使用。取而代之的是,React 17.x推荐使用新的静态方法getDerivedStateFromProps()和getDerivedStateFromError()来代替。
- 组件更新
在React 16.x版本中,React会在shouldComponentUpdate()方法返回true后,调用render()方法和componentDidUpdate()方法。但是在React 17.x版本中,如果shouldComponentUpdate()方法返回false,React仍然会调用render()方法,但是不会调用componentDidUpdate()方法。这个改变使得React在一些情况下更加灵活,同时也提高了性能。
- 事件处理器的绑定
在React 17.x版本中,React要求所有事件处理器的绑定必须在构造函数中完成。这个改变可以使得React在更新时更加高效,并且减少了一些错误的发生。
综上所述,React 17.x版本在生命周期方法的行为上与React 16.x版本相比并没有太大的变化,但是在一些细节上进行了改进,以提高性能和可靠性。同时,React 17.x版本也为未来的版本打下了基础,使得React可以更好地适应新的技术和应用场景。
测试代码同之前一致,限制只是把js换成React17.0.1,旧版生命周期函数在17.x中仍然可以使用,但是由警告,如下所示:
react-dom.development.js:61 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.
点击链接,跳转到官网。大意是在之后发布的版本中,这3个函数会加上UNSAFE_前缀。不是代表它们不安全,而是在未来的版本中使用可能由bug,特别是在异步渲染中。
3 getDerivedStateFromProps
getDerivedStateFromProps是React生命周期方法之一,用于根据props计算和更新state。该方法在组件挂载和更新时都会被调用。
getDerivedStateFromProps(props, state)方法接收两个参数,props表示组件的props,state表示组件的state。该方法必须返回一个对象,用于更新组件的state。
在React 16.x版本中,getDerivedStateFromProps通常被用于解决props和state不同步的问题,特别是当组件的props和state都受到外部因素的影响时。例如,当组件从父组件中接收props并更新自己的state时,可以使用getDerivedStateFromProps方法来保持props和state的同步。
在React 17.x版本中,getDerivedStateFromProps方法不再推荐用于处理props和state同步的问题。取而代之的是,React推荐使用useEffect() hook或者componentDidUpdate()方法来处理这个问题。
需要注意的是,getDerivedStateFromProps方法是一个静态方法,不能访问this关键字,也不能调用组件的实例方法。如果需要访问组件的实例方法或属性,可以将它们作为props传递给组件。
测试如下代码3-1所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1205_updating-生命周期(新)</title>
</head>
<body>
<div id="test"></div>
<!-- react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
/**
* 创建组件
* 生命周期函数:生命周期钩子函数
*/
class Count extends React.Component {
constructor(props) {
console.log('Count === constructor');
super(props)
// 初始化状态
this.state = {
count: 1 // 计数
}
}
/**
* +1
*/
add = () => {
this.setState({count: this.state.count + 1})
}
/**
* 不更新状态,强制更新
*/
force = () => {
console.log('Count ==== forceUpate');
this.forceUpdate()
}
/**
* 卸载组件
*/
remove = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
/**
* 获取从props派生的state
*/
static getDerivedStateFromProps(props, state) {
console.log('Count ==== getDerivedStateFromProps');
console.log(props, '----', state);
return null
// return props
}
/**
* 组件挂载完毕
*/
componentDidMount() {
console.log('Count === componentDidMount');
}
/**
* 组件是否应该被更新
*/
shouldComponentUpdate(nextProps, nextState) {
console.log('count === shouldComponentUpdate :', nextProps, '----', nextState);
return true;
}
/**
* 组件将要被更新之后
*/
componentDidUpdate(prevProps, prevState) {
console.log('count === componentDidUpdate :', prevProps, '----', prevState);
}
/**
* 组件卸载之前
*/
componentWillUnmount() {
console.log('Count === componentWillUnmount');
}
/**
* 初始化渲染,状态更新重新渲染
*/
render() {
console.log('Count === render');
const { count } = this.state
return (
<div>
<h2>当前计数:{count}</h2><br />
<button onClick={this.add}>点我+1</button><br/>
<button onClick={this.force}>不更改状态强制更新</button>
</div>
)
}
}
// 2.渲染虚拟DOM到页面
ReactDOM.render(<Count count={99}/>, document.getElementById('test'))
</script>
</body>
</html>
getDerivedStateFromProps 此方法 适用于及其罕见的案例,即state的值在任何时候都取决于props,目前在开发中很少使用。
4 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate是React生命周期方法之一,在组件更新之前被调用,用于获取组件更新前的一些信息。该方法的返回值会作为第三个参数传递给componentDidUpdate()方法。
getSnapshotBeforeUpdate(prevProps, prevState)方法接收两个参数,prevProps表示组件更新前的props,prevState表示组件更新前的state。该方法必须返回一个值,用于在componentDidUpdate()方法中进行处理。
在React 16.x版本中,getSnapshotBeforeUpdate通常被用于获取组件更新前的某些信息,例如组件的滚动位置。获取到的信息可以在componentDidUpdate()方法中进行处理,例如恢复滚动位置等。
在React 17.x版本中,getSnapshotBeforeUpdate方法的行为与React 16.x版本相同,但是该方法不再推荐使用。取而代之的是,React推荐使用useEffect() hook或者componentDidUpdate()方法来处理获取组件更新前的信息。
需要注意的是,getSnapshotBeforeUpdate方法在组件挂载时不会被调用,只有在组件更新时才会被调用。同时,如果shouldComponentUpdate()方法返回false,getSnapshotBeforeUpdate()方法也不会被调用。
测试代码同上,加上getSnapshotBeforeUpdate方法,新增代码4-1如下所示:
/**
* 获取更新前的快照
*/
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('Count ==== getSnapshotBeforeUpdate');
console.log(prevProps, '----', prevState);
return '你好呀';
}
/**
* 组件更新之后
*/
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('count === componentDidUpdate :', prevProps, '----', prevState);
console.log('snapshot: ', snapshot);
}
效果如下图4-1所示
下面通过一个小案例,演示下该生命周期函数的应用场景。场景描述,又一个新闻列表,持续产生新的新闻,新闻出现在最上边。新闻盒子有固定高度,当新闻盒子满时,超出的旧新闻隐藏。我们希望实现当我们滚动到第几条新闻时,展示区域固定展示我们到的新闻,新新闻持续产生。
效果图示如下4-2所示:
实现代码如下4-2所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1206_getSnapshotBeforeUpdate应用场景-生命周期(新)</title>
<style>
.list{
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news{
height: 30px;
}
</style>
</head>
<body>
<div id="news"></div>
<!-- react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
/**
* 创建组件
* 生命周期函数:生命周期钩子函数
*/
class NewsList extends React.Component {
state = {newsArr: []}
componentDidMount() {
setInterval(() => {
// 获取原状态
const {newsArr} = this.state
const news = '新闻' + (newsArr.length + 1)
// 更新状态
this.setState({newsArr: [news, ...newsArr]})
}, 1000);
}
getSnapshotBeforeUpdate() {
// 获取内容高度
return this.refs.list.scrollHeight
}
componentDidUpdate(prevProps, prevState, prevHeight) {
// 滚动条持续滚动
this.refs.list.scrollTop += this.refs.list.scrollHeight - prevHeight
}
/**
* 初始化渲染,状态更新重新渲染
*/
render() {
return (
<div className="list" ref='list'>
{
this.state.newsArr.map((n, index) => {
return <div className="news" key={index}>{n}</div>
})
}
</div>
)
}
}
// 2.渲染虚拟DOM到页面
ReactDOM.render(<NewsList/>, document.getElementById('news'))
</script>
</body>
</html>
后记
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/react-study
参考:
[1]React视频教程[CP/OL].2020-12-15.p43-p47.
[2]React官网[CP/OL].
[2]ChatGPT[CP/OL].