一、生命周期钩子函数
- 生命周期的概念:每个组件的实例,从创建、运行、到销毁,在这个过程中会出发一些列事件,这些事件就叫做组件的生命周期函数;
- React组件生命周期分为三部分:
- 组件创建阶段:
- static 开头的 只会执行一次
- constructor 构造器 只会执行一次
- getDerivedStateFromProps: 当子组件接收到新的props会执行,作用:将传递的props映射到state里面 会执行多次
- render 构建虚拟dom,但是此时虚拟dom还没有渲染到页面 会执行多次
- componentDidMount: 组建的虚拟dom已经挂载到页面 只会执行一次
- 组件运行阶段:根据 props 属性 或 state 状态的改变,有选择性的执行0到多次
- getDerivedStateFromProps:组件将要接收到新的props属性
- shouldComponentUpdate:组件是否需要被更新,返回值是true或者false。此时可以获取最新的props和state数据
- render: 重新更新渲染组件的虚拟dom
- getSnapshotBeforeUpdate:在最近一次渲染提交到 DOM 节点之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置),返回值将作为参数传递给componentDidUpdate
- componentDidUpdate: 组件完成了更新,此时页面已经是最新的了
- 组件销毁阶段:只执行一次
- componentWillUnmount: 组件将要被销毁,此时组件还可以被使用
- 组件创建阶段:
React生命周期的回调函数总结成表格如下:
二、生命周期函数详解及defaultProps、类型校验
在组件创建之前,会先初始化默认的props属性,这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。
#1.Counter.jsx
import React from 'react'
import ReactTypes from 'prop-types'
class Counter extends React.Component {
//----------------------------组件的创建阶段------------------------------------------
//1.生命周期第一个执行的是静态属性(只会执行一次)
// 静态属性:设置props的初始值(在使用当前组件的时候,没有给当前组件传递props的时候,该初始值会生
效)
static defaultProps = {
initcount: 1000
}
static propTypes = {
// 使用 prop-types 包 来定义 initcount 为 number 类型
// isRequired 表示这个props属性是必须要传递的
initcount: ReactTypes.number.isRequired
}
//2.生命周期第二个执行的是constructor 构造函数 (执行一次)
// 在创建组件对象的时候会自动调用构造函数
constructor(props) {
console.log("constructor")
super(props)
//在构造函数中指定组件的私有数据
this.state = {
count: props.initcount
}
this.divRef = React.createRef()
}
//3.当子组件接收到新的props会执行(执行多次)
`注意:在react16.4版本以后,setState修改state数据触发forceUpdate方法之后也会触发这个方法
此时当我们在componentDidUpdate方法中通过this.setState()方法修改state数据之后会导致页面
没有更新的bug,对应此bug,我们可以给state新增prevCount字段来解决`
// 在该方法中不能通过this.setSate来修改state数据,因为在static修饰的方法中就没有this
//作用:将传递的props映射到state里面
//参数: nextProps 外部传递过来的props属性
// prevState 之前的state状态
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps", nextProps, prevState)
//获取到父组件中传递过来的initcount值
const { initcount } = nextProps;
//当父组件中传递过来的initcount发生改变了
if (initcount != prevState.prevCount) {
//在方法中返回{count:initcount} 替换掉当前state中的 count值
return {
count: initcount,
prevCount:initcount
}
}
//如果父组件中传递过来的initcount没有变化,就返回一个空对象(此时不对当前的state做任何修改)
return null
}
//4.创建虚拟dom,但是虚拟dom还没有挂载到页面 (执行多次)
// render函数中不能使用setState()来修改state的数据,因为会死循环
render() {
console.log("render", this.divRef.current) //null
return (<div ref={this.divRef}>
哈哈Counter {this.state.count}
</div>)
}
//5.虚拟dom已经挂载到页面,此时可以获取到最新的页面 (执行一次)
componentDidMount() {
console.log("componentDidMount", this.divRef.current, this.state) //null
}
//----------------------------组件的运行阶段(执行多次)----------------------------
// 第一个会执行:static getDerivedStateFromProps
// 当父组件给子组件传递的props发生变化的时候,会执行该方法。
// 在react16.4版本以后,当前组件的state发生变化,会触发forceUpdate方法,然后还是会触发该方法
// 第二个会执行: shouldComponentUpdate
// 在这个函数中,我们可以设置指定条件下去更新页面,指定条件下不更新页面。这个函数中入参props和
state都是最新的
// 在该方法中不能通过this.setSate来修改state数据
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate", nextProps, nextState)
// 在 shouldComponentUpdate 中要求必须返回一个布尔值
// 在 shouldComponentUpdate 中,如果返回的值是 false,则不会继续执行后续的生命周期函数,而
是直接退回到了运行中的状态,此时后续的render函数并没有被调用,因此页面不会被更新,但是组件的 state 状
态,却被修改了;
// return nextState.count % 2 == 0 ? true : false;
return true;
}
//第三个会执行:render 此时会重新构建虚拟dom,但是新的虚拟dom还没有渲染到页面
//第四个会执行getSnapshotBeforeUpdate 函数
// 在该方法中不能通过this.setSate来修改state数据
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate")
return {}
}
//第五个执行componentDidUpdate 函数
// 在该方法中不能通过this.setSate来修改state数据
//表示组件已经更新完毕 此时可以获取到最新的页面
componentDidUpdate() {
console.log("componentDidUpdate", this.state)
}
//-------------------组件的销毁阶段--------------------------
componentWillUnmount() {
console.log("componentWillUnmount")
}
}
export default Counter;
#App.jsx
import React from 'react'
import Counter from '@/components/Counter'
class App2 extends React.Component {
constructor() {
super();
this.state = {
num:11
}
}
handlerClick=()=>{
this.setState({
num:111
})
}
render() {
return (<div>
App2
<Counter initcount={this.state.num}></Counter>
<button onClick={this.handlerClick}>点我该</button>
</div>)
}
}
export default App2;
三、getSnapshotBeforeUpdate方法
#1.SnapshotSample.jsx
import React from 'react'
import cssobj from './snap.less'
class SnapshotSample extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [],//用于保存子div
}
}
handleMessage() {//用于增加msg
this.setState(pre => ({
messages: [`msg: ${pre.messages.length}`, ...pre.messages],
}))
}
componentDidMount() {
for (let i = 0; i < 20; i++) this.handleMessage();//初始化20条
this.timeID = window.setInterval(() => {//设置定时器
if (this.state.messages.length > 200) {//大于200条,终止
window.clearInterval(this.timeID);
return;
} else {
this.handleMessage();
}
}, 1000)
}
componentWillUnmount() {//清除定时器
window.clearInterval(this.timeID);
}
//很关键的,我们获取当前rootNode的scrollHeight,传到componentDidUpdate 的参数perScrollHeight
//在render方法调用之后,在componentDidUpdate之前执行,我们可以在这个方法中获取到元素的滚动位置的
信息
//还可以在该方法中做一些样式的微调
getSnapshotBeforeUpdate() {
//console.log(this.rootNode.clientHeight)
//this.rootNode.style.backgroundColor = "red";
return this.rootNode.scrollHeight;
}
componentDidUpdate(perProps, perState, perScrollHeight) {
const curScrollTop = this.rootNode.scrollTop;
if (curScrollTop < 5) return;
this.rootNode.scrollTop = curScrollTop + (this.rootNode.scrollHeight - perScrollHeight);
//加上增加的div高度,就相当于不动
}
render() {
return (
<div className={cssobj.wrap} ref={node => (this.rootNode = node)} >
{this.state.messages.map(msg => (
<div key={msg}>{msg} </div>
))}
</div>
);
}
}
export default SnapshotSample;
#2.snap.less
.wrap{
height: 100px;
width :200px;
padding: 1px solid #eee;
overflow:auto;
}
#3.App.jsx
import React from 'react'
import SnapshotSample from '@/components/SnapshotSample'
class App2 extends React.Component {
constructor() {
super();
this.state = {
num:11
}
}
handlerClick=()=>{
this.setState({
num:111
})
}
render() {
return (<div>
App2
<SnapshotSample></SnapshotSample >
</div>)
}
}
export default App2;