在新版本的生命周期中废弃了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 '。
使用getDerivedStateFromProps
<!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
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>测试生命周期函数getSnapshotBeforeUpdate</title> <style> .list { width: 200px; height: 150px; background-color: pink; overflow: auto; } .news { width: 100%; height: 30px; } </style> </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 { 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()
触发—初次渲染
- 1===>
constructor()
- 2===>
getDerivedStateFromProps()
- 3===>
render()
- 4===>
componentDidMount()
2、更新阶段:由组件内部this.setState()
或者父组件render
触发
- 1===>
getDerivedStateFromProps
- 2===>
shouldComponentUpdate()
- 3===>
render()
- 4===>
getSnapshotBeforeUpdate()
- 5===>
componentDidUpdate()
3、卸载组件:由ReactDOM.unmountComponentAtNode()
触发
- 1===>
componentWillUnmount()
- 重要的钩子
1、render
: 初始化渲染或更新渲染调用
2、componentDidMount
: 开启监听,发送ajax请求
3、componentWillUnmount
:做一些收尾的工作,如:销毁监听,清理定时器
- 即将废弃的钩子
1、componentWillMount
2、componentWillUpdate
3、componentWillReceiveProps
在17.x
版本使用需要加上UNSAFE_
前缀使用,18.x
版本彻底废弃,不建议使用。