一、生命周期钩子函数

  • 生命周期的概念:每个组件的实例,从创建、运行、到销毁,在这个过程中会出发一些列事件,这些事件就叫做组件的生命周期函数;
  • 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: 组件将要被销毁,此时组件还可以被使用1574654437154.png

React生命周期的回调函数总结成表格如下:
1574666830021.png

二、生命周期函数详解及defaultProps、类型校验

在组件创建之前,会先初始化默认的props属性,这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。

  1. #1.Counter.jsx
  2. import React from 'react'
  3. import ReactTypes from 'prop-types'
  4. class Counter extends React.Component {
  5. //----------------------------组件的创建阶段------------------------------------------
  6. //1.生命周期第一个执行的是静态属性(只会执行一次)
  7. // 静态属性:设置props的初始值(在使用当前组件的时候,没有给当前组件传递props的时候,该初始值会生
  8. 效)
  9. static defaultProps = {
  10. initcount: 1000
  11. }
  12. static propTypes = {
  13. // 使用 prop-types 包 来定义 initcount 为 number 类型
  14. // isRequired 表示这个props属性是必须要传递的
  15. initcount: ReactTypes.number.isRequired
  16. }
  17. //2.生命周期第二个执行的是constructor 构造函数 (执行一次)
  18. // 在创建组件对象的时候会自动调用构造函数
  19. constructor(props) {
  20. console.log("constructor")
  21. super(props)
  22. //在构造函数中指定组件的私有数据
  23. this.state = {
  24. count: props.initcount
  25. }
  26. this.divRef = React.createRef()
  27. }
  28. //3.当子组件接收到新的props会执行(执行多次)
  29. `注意:在react16.4版本以后,setState修改state数据触发forceUpdate方法之后也会触发这个方法
  30. 此时当我们在componentDidUpdate方法中通过this.setState()方法修改state数据之后会导致页面
  31. 没有更新的bug,对应此bug,我们可以给state新增prevCount字段来解决`
  32. // 在该方法中不能通过this.setSate来修改state数据,因为在static修饰的方法中就没有this
  33. //作用:将传递的props映射到state里面
  34. //参数: nextProps 外部传递过来的props属性
  35. // prevState 之前的state状态
  36. static getDerivedStateFromProps(nextProps, prevState) {
  37. console.log("getDerivedStateFromProps", nextProps, prevState)
  38. //获取到父组件中传递过来的initcount值
  39. const { initcount } = nextProps;
  40. //当父组件中传递过来的initcount发生改变了
  41. if (initcount != prevState.prevCount) {
  42. //在方法中返回{count:initcount} 替换掉当前state中的 count值
  43. return {
  44. count: initcount,
  45. prevCount:initcount
  46. }
  47. }
  48. //如果父组件中传递过来的initcount没有变化,就返回一个空对象(此时不对当前的state做任何修改)
  49. return null
  50. }
  51. //4.创建虚拟dom,但是虚拟dom还没有挂载到页面 (执行多次)
  52. // render函数中不能使用setState()来修改state的数据,因为会死循环
  53. render() {
  54. console.log("render", this.divRef.current) //null
  55. return (<div ref={this.divRef}>
  56. 哈哈Counter {this.state.count}
  57. </div>)
  58. }
  59. //5.虚拟dom已经挂载到页面,此时可以获取到最新的页面 (执行一次)
  60. componentDidMount() {
  61. console.log("componentDidMount", this.divRef.current, this.state) //null
  62. }
  63. //----------------------------组件的运行阶段(执行多次)----------------------------
  64. // 第一个会执行:static getDerivedStateFromProps
  65. // 当父组件给子组件传递的props发生变化的时候,会执行该方法。
  66. // 在react16.4版本以后,当前组件的state发生变化,会触发forceUpdate方法,然后还是会触发该方法
  67. // 第二个会执行: shouldComponentUpdate
  68. // 在这个函数中,我们可以设置指定条件下去更新页面,指定条件下不更新页面。这个函数中入参props和
  69. state都是最新的
  70. // 在该方法中不能通过this.setSate来修改state数据
  71. shouldComponentUpdate(nextProps, nextState) {
  72. console.log("shouldComponentUpdate", nextProps, nextState)
  73. // 在 shouldComponentUpdate 中要求必须返回一个布尔值
  74. // 在 shouldComponentUpdate 中,如果返回的值是 false,则不会继续执行后续的生命周期函数,而
  75. 是直接退回到了运行中的状态,此时后续的render函数并没有被调用,因此页面不会被更新,但是组件的 state
  76. 态,却被修改了;
  77. // return nextState.count % 2 == 0 ? true : false;
  78. return true;
  79. }
  80. //第三个会执行:render 此时会重新构建虚拟dom,但是新的虚拟dom还没有渲染到页面
  81. //第四个会执行getSnapshotBeforeUpdate 函数
  82. // 在该方法中不能通过this.setSate来修改state数据
  83. getSnapshotBeforeUpdate() {
  84. console.log("getSnapshotBeforeUpdate")
  85. return {}
  86. }
  87. //第五个执行componentDidUpdate 函数
  88. // 在该方法中不能通过this.setSate来修改state数据
  89. //表示组件已经更新完毕 此时可以获取到最新的页面
  90. componentDidUpdate() {
  91. console.log("componentDidUpdate", this.state)
  92. }
  93. //-------------------组件的销毁阶段--------------------------
  94. componentWillUnmount() {
  95. console.log("componentWillUnmount")
  96. }
  97. }
  98. export default Counter;
  99. #App.jsx
  100. import React from 'react'
  101. import Counter from '@/components/Counter'
  102. class App2 extends React.Component {
  103. constructor() {
  104. super();
  105. this.state = {
  106. num:11
  107. }
  108. }
  109. handlerClick=()=>{
  110. this.setState({
  111. num:111
  112. })
  113. }
  114. render() {
  115. return (<div>
  116. App2
  117. <Counter initcount={this.state.num}></Counter>
  118. <button onClick={this.handlerClick}>点我该</button>
  119. </div>)
  120. }
  121. }
  122. export default App2;

三、getSnapshotBeforeUpdate方法

  1. #1.SnapshotSample.jsx
  2. import React from 'react'
  3. import cssobj from './snap.less'
  4. class SnapshotSample extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.state = {
  8. messages: [],//用于保存子div
  9. }
  10. }
  11. handleMessage() {//用于增加msg
  12. this.setState(pre => ({
  13. messages: [`msg: ${pre.messages.length}`, ...pre.messages],
  14. }))
  15. }
  16. componentDidMount() {
  17. for (let i = 0; i < 20; i++) this.handleMessage();//初始化20条
  18. this.timeID = window.setInterval(() => {//设置定时器
  19. if (this.state.messages.length > 200) {//大于200条,终止
  20. window.clearInterval(this.timeID);
  21. return;
  22. } else {
  23. this.handleMessage();
  24. }
  25. }, 1000)
  26. }
  27. componentWillUnmount() {//清除定时器
  28. window.clearInterval(this.timeID);
  29. }
  30. //很关键的,我们获取当前rootNode的scrollHeight,传到componentDidUpdate 的参数perScrollHeight
  31. //在render方法调用之后,在componentDidUpdate之前执行,我们可以在这个方法中获取到元素的滚动位置的
  32. 信息
  33. //还可以在该方法中做一些样式的微调
  34. getSnapshotBeforeUpdate() {
  35. //console.log(this.rootNode.clientHeight)
  36. //this.rootNode.style.backgroundColor = "red";
  37. return this.rootNode.scrollHeight;
  38. }
  39. componentDidUpdate(perProps, perState, perScrollHeight) {
  40. const curScrollTop = this.rootNode.scrollTop;
  41. if (curScrollTop < 5) return;
  42. this.rootNode.scrollTop = curScrollTop + (this.rootNode.scrollHeight - perScrollHeight);
  43. //加上增加的div高度,就相当于不动
  44. }
  45. render() {
  46. return (
  47. <div className={cssobj.wrap} ref={node => (this.rootNode = node)} >
  48. {this.state.messages.map(msg => (
  49. <div key={msg}>{msg} </div>
  50. ))}
  51. </div>
  52. );
  53. }
  54. }
  55. export default SnapshotSample;
  56. #2.snap.less
  57. .wrap{
  58. height: 100px;
  59. width :200px;
  60. padding: 1px solid #eee;
  61. overflow:auto;
  62. }
  63. #3.App.jsx
  64. import React from 'react'
  65. import SnapshotSample from '@/components/SnapshotSample'
  66. class App2 extends React.Component {
  67. constructor() {
  68. super();
  69. this.state = {
  70. num:11
  71. }
  72. }
  73. handlerClick=()=>{
  74. this.setState({
  75. num:111
  76. })
  77. }
  78. render() {
  79. return (<div>
  80. App2
  81. <SnapshotSample></SnapshotSample >
  82. </div>)
  83. }
  84. }
  85. export default App2;