在新版本的生命周期中废弃了3个钩子函数,新增了2个钩子函数:
从上图可知,新增的2个钩子函数是我们从来没有见过的:
getDerivedStateFromProps
getSnapshotBeforeUpdate
废弃的3个钩子函数是:
componentWillMount
componentWillUpdate
componentWillReceiveProps
根据官方文档说明:在React17
版本中这废弃的3个钩子函数被重命名了,需要加一个前缀UNSAFE_
才能使用。但是在18.x
版本中就会彻底删除这3个钩子函数。以下是你在新版的React中继续使用这3个钩子函数时报的警告:
警告:componentWillMount已重命名,不建议使用。详情见https://reactjs.org/link/unsafe-component-lifecycles。
移动带有副作用的代码到componentDidMount,并在构造函数中设置初始状态。
将componentWillMount重命名为UNSAFE_componentWillMount以在非严格模式下抑制此警告。在React 18.x,只有UNSAFE_名称将工作。要将所有已弃用的生命周期重命名为它们的新名称,您可以在项目源文件夹中运行' npx react-codemod rename-unsafe-lifecycles '。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生命周期Count</title>
</head>
<body>
<!-- 准备好员工“容器” -->
<div id="app"></div>
<!-- 引入ReactJS核心库 -->
<script type="text/javascript" src="../JS/新版本/react.development.js"></script>
<!-- 引入React-DOM核心库,用于操作DOM -->
<script type="text/javascript" src="../JS/新版本/react-dom.development.js"></script>
<!-- 引入Babel,用于编译jsx为js -->
<script type="text/javascript" src="../JS/新版本/babel.min.js"></script>
<!-- 此处类型为babel -->
<script type="text/babel">
class Count extends React.Component {
constructor(props){
console.log('Count--constructor')
super(props)
// 初始化状态
this.state = { count: 0 }
}
// 卸载组件
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
// 求和加1
add = () => {
let { count } = this.state
count += 1
this.setState({count})
}
// 强制更新
force = () => {
this.forceUpdate()
}
getDerivedStateFromProps(){
console.log("Count--getDerivedStateFromProps")
}
// 组件挂载完毕
componentDidMount() {
console.log("Count--componentDidMount")
}
// 判断组件是否需要更新,返回一个false或者true
shouldComponentUpdate(){
console.log("Count--shouldComponentUpdate")
return true
}
// 组件更新完毕
componentDidUpdate(){
console.log("Count--componentDidUpdate")
}
// 组件将要被卸载
componentWillUnmount(){
console.log("Count--componentWillUnmount")
}
// 渲染DOM结构
render () {
console.log("Count--render")
const {count} = this.state
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={this.add}>点我+1</button>
<button onClick={this.force}>强制更新</button>
<button onClick={this.death}>卸载组件</button>
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Count />, document.getElementById('app'))
</script>
</body>
</html>
我们还是使用Count
组件作为案例使用。但是报错了:
Warning: Count: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.
大概意思是:警告:Count: getDerivedStateFromProps()
被定义为实例方法,将被忽略。相反,应该将其声明为静态方法。
意思是这个一个静态方法,我们需要加一个关键字:static
static getDerivedStateFromProps(){
console.log("Count--getDerivedStateFromProps")
}
但是又有了一个新的错误:
Warning: Count.getDerivedStateFromProps(): A valid state object (or null) must be returned. You have returned undefined.
大概意思是:Count.getDerivedStateFromProps()
:必须返回一个有效的状态对象(或null)。您已经返回undefined
。
这个错误是说我们要返回一个和state
对象或者是null
,只有这2种选择,而不是undefined
static getDerivedStateFromProps(){
console.log("Count--getDerivedStateFromProps")
return null
}
这次我们返回null
试试水,发现没有报错,且打印出了生命周期顺序:
Count--constructor
Count--getDerivedStateFromProps
Count--render
Count--componentDidMount
那什么又是返回一个状态对象呢?我们看看官网:
static getDerivedStateFromProps()
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现
组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。
派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:
- 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用
componentDidUpdate
。- 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。
- 如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用
key
使组件完全不受控 代替。此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在
getDerivedStateFromProps()
和其他 class 方法之间重用代码。请注意,不管原因是什么,都会在每次渲染前触发此方法。这与
UNSAFE_componentWillReceiveProps
形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用setState
时。
我们发现一句话非常重要:此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。
我们以此来修改代码:
static getDerivedStateFromProps(props,state){
console.log("Count--getDerivedStateFromProps",props,state)
return props
}
//...
ReactDOM.render(<Count count={110} />, document.getElementById('app'))
我们在标签属性上面写了一个count={110}
,并在该函数中返回props
,此时我们发现页面中显示的内容是:110,且点击自增按钮,页面不发生任何改变。但是这个函数我们使用的极少,就此作为了解。
getSnapshotBeforeUpdate(){
console.log("Count--getSnapshotBeforeUpdate")
}
此时我们更新组件时报了一个错误:
Warning: Count.getSnapshotBeforeUpdate(): A snapshot value (or null) must be returned. You have returned undefined.
大概意思是:警告:Count.getSnapshotBeforeUpdate()
:必须返回快照值(或null
)。您已经返回undefined
。
和之前错误类似,同样我们返回null试试水:
getSnapshotBeforeUpdate(){
console.log("Count--getSnapshotBeforeUpdate")
return null
}
此时我们更新组件不报错,且打印出了执行的生命周期:
Count--getDerivedStateFromProps {count: 110} {count: 2}
Count--shouldComponentUpdate
Count--render
Count--getSnapshotBeforeUpdate
Count--componentDidUpdate
同样的问题:什么是返回一个快照值?我们同样看看官网:
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到DOM
节点)之前调用。它使得组件能在发生更改之前从DOM
中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给componentDidUpdate()
。此用法并不常见,但它可能出现在
UI
处理中,如需要以特殊方式处理滚动位置的聊天线程等。应返回 snapshot 的值(或
null
)。
意思是它能在组件即将渲染到页面之前,我们可以通过它拿到一些DOM
信息,并传递给componentDidUpdate()
使用。
我们写一个案例说明:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试生命周期函数getSnapshotBeforeUpdatetitle>
<style>
.list {
width: 200px;
height: 150px;
background-color: pink;
overflow: auto;
}
.news {
width: 100%;
height: 30px;
}
style>
head>
<body>
<div id="app">div>
<script type="text/javascript" src="../JS/新版本/react.development.js">script>
<script type="text/javascript" src="../JS/新版本/react-dom.development.js">script>
<script type="text/javascript" src="../JS/新版本/babel.min.js">script>
<script type="text/babel">
class Count extends React.Component {
listRef = React.createRef()
state = { newArr: [] }
getSnapshotBeforeUpdate () {
return this.listRef.current.scrollHeight
}
// 组件更新完毕
componentDidUpdate (prevProps,prevState,snapshotValue) {
console.log("Count--componentDidUpdate",snapshotValue)
this.listRef.current.scrollTop += this.listRef.current.scrollHeight - snapshotValue
}
// 组件挂载完毕
componentDidMount () {
setInterval(() => {
let { newArr } = this.state
const news = "新闻" + (newArr.length + 1)
newArr = [news, ...newArr]
this.setState({ newArr })
}, 1000)
}
// 渲染DOM结构
render () {
const { newArr } = this.state
return (
<div className="list" ref={this.listRef}>
{
newArr.map((val, idx) => {
return <div className="news" key={idx}>{val}</div>
})
}
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Count count={110} />, document.getElementById('app'))
script>
body>
html>
以上组件的效果如下:
就是让滚动条一直处于底部,让最初的新闻信息一直停留在当前可见区域。大概主要的代码逻辑如下:
// 组件挂载完毕
componentDidMount () {
setInterval(() => {
let { newArr } = this.state
const news = "新闻" + (newArr.length + 1)
newArr = [news, ...newArr]
this.setState({ newArr })
}, 1000)
}
设置定时器,每个一秒就增加一条新闻信息。
getSnapshotBeforeUpdate () {
return this.listRef.current.scrollHeight
}
在组件更新快要完成时,获取组件的滚动条高度。
// 组件更新完毕
componentDidUpdate (prevProps,prevState,snapshotValue) {
console.log("Count--componentDidUpdate",snapshotValue)
this.listRef.current.scrollTop += this.listRef.current.scrollHeight - snapshotValue
}
在组件更新完成时,设置滚动条的scrollTop
属性,使得内容一直在可见区域。
1、初始化阶段:由ReactDOM.render()
触发—初次渲染
constructor()
getDerivedStateFromProps()
render()
componentDidMount()
2、更新阶段:由组件内部this.setState()
或者父组件render
触发
1===>getDerivedStateFromProps
2===>shouldComponentUpdate()
3===>render()
4===>getSnapshotBeforeUpdate()
5===>componentDidUpdate()
3、卸载组件:由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount()
1、render
: 初始化渲染或更新渲染调用
2、componentDidMount
: 开启监听,发送ajax请求
3、componentWillUnmount
:做一些收尾的工作,如:销毁监听,清理定时器
1、componentWillMount
2、componentWillUpdate
3、componentWillReceiveProps
在17.x
版本使用需要加上UNSAFE_
前缀使用,18.x
版本彻底废弃,不建议使用。