您当前的位置: 首页 > 

暂无认证

  • 20粉丝

    0关注

    0博文

    0收益

  • 20浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【React】深入理解React组件生命周期----图文详解(含代码)

发布时间:2022-05-30 21:39:19 ,浏览量:83

文章目录
  • 📌组件的生命周期
    • 1️⃣ 理解
    • 2️⃣ 案例
    • 3️⃣生命周期流程图(旧)
      • A. 实现setState 和 forceUpdate 流程
      • B. 实现组件之间传值流程
    • 4️⃣ 对比新旧生命周期
    • 5️⃣ 生命周期流程图(新)
      • A. getDerivedStateFromProps(几乎用不到,使用场景及其罕见)
      • B. getSnapshotBeforeUpdate
      • C.getSnapshotBeforeUpdate使用场景
    • 6️⃣ 重要的三个钩子
    • 7️⃣ 即将废弃的钩子

📌组件的生命周期

先来看个例子引出生命周期

1️⃣ 理解
  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列钩子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2️⃣ 案例

需求:定义组件实现以下功能:

  1. 让指定的文本做显示/隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时2S
  3. 点击”不活了“按钮从界面中卸载组件

效果如下:

在这里插入图片描述

具体代码实现:


    // 创建组件
    // 生命周期回调函数  生命周期钩子函数(我定义的,react帮我调用的)
    class Life extends React.Component {
      //  opacity属性设置一个div元素的透明度级别,默认值为1(完全不透明)
      state = { opacity: 1 }

      death = () => {
        // 清除定时器
        // clearInterval(this.timer)
        // 卸载组件
        // unmountComponentAtNode:卸载组件在哪个节点里
     ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }

      // 组件挂载完毕之后调用
      componentDidMount() {
        // 定时器是挂载在window上的
        // 当组件卸载完之后,定时器也不会消失
        this.timer = setInterval(() => {
          // 获取原状态
          let { opacity } = this.state
          // 减小0.1
          opacity -= 0.1
          if (opacity  {
        //   获取原状态
        //   let { opacity } = this.state
        //   减小0.1
        //   opacity -= 0.1
        //   if (opacity  {
        // 获取原状态
        const { count } = this.state
        // 更新状态
        this.setState({ count: count + 1 })
      }

      // 卸载组件按钮的回调
      death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }

      // 强制更新按钮的回调
      force = () => {
        this.forceUpdate() //通过forceUpdate方法可以做到即使不使用state属性也可以更新界面
      }

      // 初始化阶段
      // 组件将要挂载之前的钩子
      componentWillMount() {
        console.log('Count---componentWillMount')
      }

      // 组件挂载完毕的钩子
      componentDidMount() {
        console.log('Count---componentDidMount')
      }

      // 更新阶段
      // 控制组件更新的“阀门” 。
      // 如果不写这个钩子,react底层会自动补一个默认返回值为true的shouldComponentUpdate(),表示可以更新,
      // 如果返回值为false,则不会执行后续的生命周期函数
      shouldComponentUpdate() {
        console.log('Count---shouldComponentUpdate')
        return true
      }

      //组件将要更新的钩子
      componentWillUpdate() {
        console.log('Count---componentWillUpdate')
      }

      // 组件更新完毕的钩子
      componentDidUpdate() {
        console.log('Count---componentDidUpdate')
      }
    
      // 卸载阶段
      // 组件将要卸载的钩子
      componentWillUnmount() {
        console.log('Count---componentWillUnmount')
      }

      render() {
        console.log('Count---render')
        const { count } = this.state
        return (
          
            当前求和为:{count}
            点我+1
            卸载组件
            不更改任何状态中的数据,强制更新一下
          
        )
      }
    }

    // 渲染组件
    ReactDOM.render(, document.getElementById('test'))
  
  • 组件挂载到页面时:

    constructor(当前类的构造函数) => componentWillMount(组件将要挂载之前的钩子) => render(渲染组件到页面) => componentDidMount (组件挂载完毕的钩子)

  • 组件更新页面时(setState()):

    shouldComponentUpdate(控制组件更新的“阀门”:如果返回值为false,则不会执行后续的生命周期函数)=> componentWillUpdate(组件将要更新的钩子)=> render(渲染组件到页面)=> componentDidUpdate(组件更新完毕的钩子)

  • 组件强制更新页面时(forceUpdate()):绕开了shouldComponentUpdate阀门,即使不更新state中的数据,shouldComponentUpdate的返回值为false,也会更新页面

    componentWillUpdate(组件将要更新的钩子)=> render(渲染组件到页面)=> componentDidUpdate(组件更新完毕的钩子)

  • 组件卸载:

    componentWillUnmount(组件将要卸载的钩子)

总结:

生命周期的三个阶段(旧)如下:

  • 初始化阶段:由ReactDOM.render()触发—页面初次渲染
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount() ====>常用(一般在这个钩子中做一些初始化的事,例如:开启定时器,发送网络请求,订阅消息)(出生那天)
  • 更新阶段:由组件内部this.setState()父组件重新render触发
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render() ====>必须使用的一个
    • componentDidUpdate()
  • 卸载阶段:由ReactDOM.unmountComponentAtNode()触发
    • componentWillUnmount() ====>常用(一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息)(死亡那天)
B. 实现组件之间传值流程

案例:

需求:

  1. 点击换车按钮,显示车从”奔驰“ 变成 ”宝马“。

效果如下:

在这里插入图片描述

代码如下:


    // 父组件render流程
    // 父组件A
    class A extends React.Component {
      // 初始化状态
      state = { carName: '奔驰' }

      changeCar = () => {
        this.setState({ carName: '宝马' })
      }

      render() {
        return (
          
            我是A组件
            {/* 
              点击换车,调用了changeCar,更改了A组件里的状态,
              导致A组件调用了render,正是父组件重新render
            */}
            换车
            {/*
            父组件A将state中的carName传给子组件B的props.carName中
            */}
            
          
        )
      }
    }

    // 子组件B
    class B extends React.Component {
      //组件将要接收新的props的钩子
      // 必须经过setStateA组件改变,然后调用render,这时B组件重新接收一个新的值,重新调用render
      componentWillReceiveProps(nextProps) {
        // this.props 表示旧的props中的数据
        // nextProps 表示传入新的props
        console.log('B---componentWillReceiveProps', nextProps)
      }

      render() {
        console.log('B---render')
        return (
          {/*
          子组件接收carName,从props中取出
          */}
          我是B组件,接收到的车是:{this.props.carName}
        )
      }
    }
    // 渲染组件
    ReactDOM.render(, document.getElementById('test'))
  

父组件render(父组件A传值给子组件B):

componentWillReceiveProps(组件将要接收新的props的钩子) => shouldComponentUpdate(控制组件更新的“阀门”) => componentWillUpdate(组件将要更新的钩子) => render(渲染) => componentDidUpdate(组件更新完毕的钩子)

componentWillReceiveProps:在第一次传入props时不调用,只有在props更新时才会执行,可以用此钩子监听子组件props的改变。

4️⃣ 对比新旧生命周期

在这里插入图片描述

17.0:新的生命周期函数删除了componentWillMountcomponentWillReceivePropscomponentWillUpdate,增加了getDerivedStateFromPropsgetSnapshotBeforeUpdate

官方说明如下: 在这里插入图片描述

5️⃣ 生命周期流程图(新)

在这里插入图片描述

A. getDerivedStateFromProps(几乎用不到,使用场景及其罕见)

在写这个生命周期函数时,要在getDerivedStateFromProps加上static,否则会报错。

在这里插入图片描述

在这里插入图片描述

原因是:在Count组件中,getDerivedStateFromProps生命周期钩子定义在了实例身上,将会被忽略,不能写成这个钩子是给实例用的,实例不能调用这个钩子。要定义成一个静态方法,加上一个关键字static。

加上static之后,页面打印出了getDerivedStateFromProps,但是页面又显示一个报错提示:

在这里插入图片描述

原因是类Count自身上的getDerivedStateFromProps函数应该返回一个状态对象,或者null,必须要有返回值。但是却返回了undefined。

当返回值是一个状态对象时:

class Count extends React.Component {
      // 构造器
      constructor(props) {
        console.log('Count---constructor')
        super(props)
        // 初始化状态
        this.state = { count: 0 }
      }
  		// 从props中得到一个派生的状态
      // 若state 的值在任何时候都取决于 props,那么可以使用
      static getDerivedStateFromProps(props,state) {
        console.log('getDerivedStateFromProps', props,state)
       //return {count:108}
        return props
      }
}
  	// 渲染组件
    ReactDOM.render(, document.getElementById('test'))

在这里插入图片描述

  • return {count:108}利用这个返回值可以指定一个状态,导致状态不能更改了,只要返回一个状态对象,对象里面包含的key,在初始状态里也有,那么就不以初始化的状态为主了,以返回的状态对象为主。

  • getDerivedStateFromProps()可以接收props参数。给Count组件传递props(< Count count=‘199’ />),然后getDerivedStateFromProps(props)可以接收到,返回props(return props),所以从props中得到一个派生的状态(不是自己写的),即 state 的值在任何时候都取决于 props,无论是初始化,或者修改都不起作用。

  • getDerivedStateFromProps()可以接收state参数,即初始化的状态对象。

言而总之,新的静态 getDerivedStateFromProps 生命周期方法在组件实例化之后以及重新渲染之前调用。它可以返回一个对象来更新 state,或者返回 null 来表示新的 props 不需要任何 state 的更新。

B. getSnapshotBeforeUpdate

如果是如下的写法:

// 在更新之前获取快照
getSnapshotBeforeUpdate(prevProps, prevState) {
   console.log('getSnapshotBeforeUpdate')
}

在这里插入图片描述

当点击 点我+1的按钮时,会出现报错,原因是:一个snapshot value,或者null必须得返回。但是却返回一个undefined。说明,这个getSnapshotBeforeUpdate钩子函数必须要有返回值

正确的写法:

// 在组件更新之前获取快照 
// 快照可以是任何值,如字符串,数组,对象,函数
getSnapshotBeforeUpdate(prevProps, prevState) {
   console.log('getSnapshotBeforeUpdate')
   return 'atguigu'   //这个返回值将作为snapshotValue传给componentDidUpdate
}

在官方文档中规定:

  • getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(备份之前的数据)(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()
  • 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
  • 应返回 snapshot 的值(或 null)。

解释:

  1. snapshot value是组件在发生更新之前从DOM中捕获的一些信息,如表格高度,滚动条的位置等。

  2. getSnapshotBeforeUpdate生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

    // 组件更新完毕的钩子
    componentDidUpdate(preProps, preState, snapshotValue) {
      console.log('Count---componentDidUpdate', preProps, preState, snapshotValue)
      //打印结果:{count:'199'} {count:0} atguigu
    }
    
C.getSnapshotBeforeUpdate使用场景

效果:

在这里插入图片描述

需求:

  • 在一个固定高度(150px)的容器中,每隔1s出现一条新闻(30px),并显示在最上方,超出高度的新闻以滚动条的形式显示,类式朋友圈,微博动态。
  • 鼠标控制滚动条滚动到某一位置,界面不动,不会自动跳转到最顶部新出现的新闻上去。

代码实现:

css:


    .list {
      width: 200px;
      height: 150px;
      background-color: skyblue;
      overflow: auto;
      /* 如果内容溢出,桌面浏览器会提供滚动条 */
    }

    .news {
      height: 30px;
    }
  

jsx:


    class NewsList extends React.Component {
      state = { newsArr: [] }

      componentDidMount() {
        setInterval(() => {
          // 获取原状态
          const { newsArr } = this.state
          // 模拟一条新闻
          const news = '新闻' + (newsArr.length + 1)
          // 更新状态
          this.setState({ newsArr: [news, ...newsArr] })
          //news:新的新闻,...newsArr:原先的新闻
        }, 1000)
      }

      getSnapshotBeforeUpdate(prevProps, prevState) {
        return this.refs.list.scrollHeight   //拿到更新之前的内容区的高度
      }

      componentDidUpdate(prevProps, prevState, height) {
        //新的新闻出现之后的高度减去之前的高度
        this.refs.list.scrollTop += this.refs.list.scrollHeight - height 
        // this.refs.list.scrollTop = this.refs.list.scrollTop + this.refs.list.scrollHeight - height
        // 6:  30 = 0 + 180 - 150
        // 7:  60 = 30+ 210 - 180
      }

      render() {
        return (
          
            
              { //map()实现遍历,map()方法定义在JavaScript的Array中,
                //它返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
                this.state.newsArr.map((news, index) => {
                  return {news}
                })
              }
              {/* 
                  
新闻7
新闻6
新闻5
新闻4
新闻3
新闻2
新闻1
*/} ); } } ReactDOM.render(, document.getElementById('test'))

scrollHeight:这个只读属性指的是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容

scrollTop:指的是一个元素的内容顶部到它的可视窗口的顶部的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0

解释代码:

  • 当新闻的条数总高度超出容器的高度时,会溢出,需要在容器的样式中加上:overflow: auto;(当内容溢出时,会提供一个滚动条)
  • 首先初始化一个空数组的状态,当组件一挂载,开启循环定时器(每隔1s显示一条新闻),获取原始的状态(看看原来有什么新闻),再模拟一条新闻,再更新状态里的newsArr这个数组,把新的新闻和原先的新闻拼接一起。
state = { newsArr: [] }

componentDidMount() {
   setInterval(() => {
   // 获取原状态
   const { newsArr } = this.state
   // 模拟一条新闻
   const news = '新闻' + (newsArr.length + 1)
   // 更新状态
   this.setState({ newsArr: [news, ...newsArr] })
   //news:新的新闻,...newsArr:原先的新闻
	}, 1000)
}
  • 状态维护好之后,要驱动页面显示,所以在render里实现动态地遍历,使用map()实现遍历,这样就能实现 每隔1s出现一条新闻,并显示在最上方, 超出高度的新闻以滚动条的形式显示 这一功能了。
{ //map()实现遍历,map()方法定义在JavaScript的Array中,
  //它返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
this.state.newsArr.map((news, index) => {
    return {news}
	})
}
  • 要实现 鼠标控制滚动条滚动到某一位置,界面不动,不会自动跳转到最顶部新出现的新闻上去 这一功能,必须用到getSnapshotBeforeUpdate这个钩子,在更新之前拿到内容区的高度 ,传给componentDidUpdate这个钩子作为第三个参数,最关键的一步,在componentDidUpdate这个钩子里实现上述这个功能:动态地调整这个this.refs.list.scrollTop往上窜地高度:拿到更新之后的内容区高度,减去更新之前的内容区高度,加上原先计算前的scrollTop 就能设置新加的元素往上窜地高度,从而实现上述功能。
componentDidUpdate(prevProps, prevState, height) {
  //新的新闻出现之后的高度减去之前的高度
  this.refs.list.scrollTop += this.refs.list.scrollHeight - height 
  // this.refs.list.scrollTop = this.refs.list.scrollTop + this.refs.list.scrollHeight - height
}

总结:

生命周期的三个阶段(新)如下:

  1. 初始化阶段:由ReactDOM.render()触发—初次渲染

    • constructor()
    • getDerivedStateFromProps()
    • render()
    • componentDidMount()
  2. 更新阶段:由组件内部this.setSate()或父组件重新render触发

    • getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode()触发

    • componentWillUnmount()
6️⃣ 重要的三个钩子
  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器
7️⃣ 即将废弃的钩子
  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

关注
打赏
1652792496
查看更多评论
广告位
立即登录/注册

微信扫码登录

0.2772s