React生命周期

组件的生命周期

React中组件有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)

初始化

在组件初始化阶段会执行

  1. constructor
  2. static getDerivedStateFromProps()
  3. componentWillMount() / UNSAFE_componentWillMount()
  4. render()
  5. componentDidMount()

更新阶段

propsstate的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:

  1. componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
  2. static getDerivedStateFromProps()
  3. shouldComponentUpdate() // react性能优化第二方案
  4. componentWillUpdate() / UNSAFE_componentWillUpdate()
  5. render()
  6. getSnapshotBeforeUpdate()
  7. componentDidUpdate()

卸载阶段

  1. componentWillUnmount()

错误处理

  1. componentDidCatch()

各生命周期详解

1.constructor(props)

React组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其他内容前,调用super(props),用来将父组件传来的props绑定到这个类中,使用this.props将会得到。

官方建议不要在constructor引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()

constructor中应当做些初始化的动作,如:初始化state,将事件处理函数绑定到类实例上,但也不要使用setState()。如果没有必要初始化state或绑定方法,则不需要构造constructor,或者把这个组件换成纯函数写法。

当然也可以利用props初始化state,在之后修改state不会对props造成任何修改,但仍然建议大家提升状态到父组件中,或使用redux统一进行状态管理。

constructor(props) {
    /* 
      1. 通过super来继承父类身上传递过来的属性,让后当前组件通过this.props接收
      2. 用来初始化一个状态
      3. 用来初始化绑定一个方法,将this传递给这个方法

      注意: 
          不写方法的触发( 订阅 )
          不写具有副作用的代码( 比如: 计时器 )
    */
  super(props);
  this.state = {
    isLiked: props.isLiked
  };
}
2.static getDerivedStateFromProps(nextProps, prevState)

getDerivedStateFromProps 是react16.3之后新增,在组件实例化后,和接受新的props后被调用。他必须返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。

如果是由于父组件的props更改,所带来的重新渲染,也会触发此方法。

调用steState()不会触发getDerivedStateFromProps()

之前这里都是使用constructor+componentWillRecieveProps完成相同的功能的

    static getDerivedStateFromProps (nextProps, prevState) {
    console.log('1', nextProps ) // 将来的属性
    console.log( '2',prevState ) //变化前的值

    /* 
      17版本将来会使用

        1. 数据请求
        2. 数据修改
            返回值就是修改后的数据
    */
    return {
      msg: 'hello'
    }
  }
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
3. componentWillMount() / UNSAFE_componentWillMount()

componentWillMount()将在React未来版本(官方说法 17.0)中被弃用。UNSAFE_componentWillMount()在组件挂载前被调用,在这个方法中调用setState()不会起作用,是由于他在render()前被调用。

为了避免副作用和其他的订阅,官方都建议使用componentDidMount()代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。

componentWillMount () {
    /* 
      1. 提供了一次 数据修改机会
      2. 进行数据请求
          axios
          fetch

      注意:  
          虽然我们这里可以进行数据请求和初始化数据的修改,但是官方建议我们写在componentDidMount中
          可以减少副作用和订阅
    */

    // fetch( 'http://localhost:3000/data.json' )自己的服务器下,前面的协议和域名端口可以省略
    fetch( '/data.json' )
      .then( res => res.json() )
      .then( data => console.log( data ))
      .catch( error => {
        if (error) throw error
      })

    this.setState({
      msg: 'componentWillMount change msg '
    })
  }
4.render()

render()方法是必需的。当他被调用时,他将计算this.propsthis.state,并返回以下一种类型:

  1. React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
  2. 字符串或数字。他们将会以文本节点形式渲染到dom中。
  3. Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
  4. null,什么也不渲染
  5. 布尔值。也是什么都不渲染。

当返回null,false,ReactDOM.findDOMNode(this)将会返回null,什么都不会渲染。

render()方法必须是一个纯函数,他不应该改变state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()返回falserender()不会被调用。

render () {
    /* 
      1. 计算this.prop   this.state 
      2. 返回一种类型
          1. React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。 
          2. 字符串或数字。他们将会以文本节点形式渲染到dom中。 
          3. Portals【'portl】。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。 
          4. null,什么也不渲染 
          5. 布尔值。也是什么都不渲染。
      3. render()方法必须是一个纯函数,他不应该改变state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 
      4. 如果shouldComponentUpdate()返回false,render()不会被调用。
      5. jsx->vdom 对象

    */
    return (
      <Fragment>
        <h3> Father组件 </h3>
        <button onClick = { this.change }> 点击 </button>
        <p> constructor : { this.state.msg } </p>
      </Fragment>
    )
   
  }
5. componentDidMount

通常在这里进行ajax请求

如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
componentDidMount在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。

componentDidMount () {
    /* 
      组件挂载结束
        1. 数据请求
        2. 数据修改

        3. 将render函数生成的vdom对象渲染成真实dom,然后挂载在 id 为 root  的容器中
    */

    fetch( '/data.json' )
      .then( res => res.json() )
      .then( data => {
        console.log( 'componentDidMount',data )
        this.setState({
          msg: data.name
        })
      })
      .catch( error => {
        if( error ) throw error 
      })

  }
6.componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)

官方建议使用getDerivedStateFromProps函数代替componentWillReceiveProps。当组件挂载后,接收到新的props后会被调用。如果需要更新state来响应props的更改,则可以进行this.propsnextProps的比较,并在此方法中使用this.setState()

如果父组件会让这个组件重新渲染,即使props没有改变,也会调用这个方法。

React不会在组件初始化props时调用这个方法。调用this.setState也不会触发。

componentWillReceiveProps ( nextProps ) {
    /* 
      触发: 属性发生改变,就会触发

      这个钩子函数一定能监听到整个当前组件的属性变化 --- > 当前组件的路由我们也可以监听到


      应用场景: 
          1. 路由监听
    */
   console.log( 'componentWillReceiveProps ');
   console.log( nextProps );//属性变化之后的值
  }
componentWillReceiveProps ( nextProps ) {
    /* 
      触发: 属性发生改变,就会触发

      这个钩子函数一定能监听到整个当前组件的属性变化 --- > 当前组件的路由我们也可以监听到


      应用场景: 
          1. 路由监听
    */
   console.log( 'componentWillReceiveProps ');
   console.log( nextProps );//属性变化之后的值
  }
7.shouldComponentUpdate(nextProps, nextState)

调用shouldComponentUpdate使React知道,组件的输出是否受stateprops的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。

在渲染新的propsstate前,shouldComponentUpdate会被调用。默认为true。这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。返回false不会阻止子组件在state更改时重新渲染。

如果shouldComponentUpdate()返回falsecomponentWillUpdate,rendercomponentDidUpdate不会被调用。

官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。

shouldComponentUpdate () {
    /* 
      决定组件是否更新
        返回值true,更新
        返回值false,不更新

        默认值是true

      这个钩子是React性能优化的关键钩子
    */
    
    // return false/true
    return true

  }
 
  //当shouldComponentUpdate 返回值为 true时,下面钩子才能执行

8.componentWillUpdate/UNSAFE_componentWillUpdate(nextProps, nextState)

在渲染新的stateprops时,UNSAFE_componentWillUpdate会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。

不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新stateprops,调用getDerivedStateFromProps

componentWillUpdate () {
    /* 
      组件即将更新   
        生成新的VDOM
    */
  }

  // render 函数     jsx --> vdom对象
9.getSnapshotBeforeUpdate()

在react render()后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。

 getSnapshotBeforeUpdate () {
    /* 
      在更新阶段,render函数调用前,执行,返回一个具体的数据给componentDidUpdate
    */
   console.log( 'getSnapshotBeforeUpdate' )
    return 1000


  }
10.componentDidUpdate(prevProps, prevState, snapshot)

在更新发生后立即调用componentDidUpdate()。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。

如果组件实现getSnapshotBeforeUpdate()生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()。否则,这个参数是undefined

 componentDidUpdate ( preState,preProps,snapshot ) {
    /* 
      组件更新结束
        1. 数据请求
        2. DOM操作( 第三方库的实例化 )
        3. 接收 getSnapshotBeforeUpdate() 第三个参数作为返回值

      使用fiber算法进行 新vdom和旧的vdom对比,生成新的patch对象

      在根据patch对象进行页面渲染

    */

    console.log('snapshot',snapshot )

    fetch( '/data.json' )
      .then( res => res.json())
      .then( data => console.log( 'componentDidUpdate',data ))
      .catch( error => {
        if( error ) console.log( error )
      })

    document.querySelector('h3').style.background = 'red'

    console.log( 'componentDidUpdate' )
  }

11.componentWillUnmount()

在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount中创建的任何监听。
内部销毁

import React,{ Component,Fragment } from 'react'
import ReactDOM from 'react-dom'


class Father extends Component{

  constructor ( props ) {
    super( props )            
    this.state = {
      msg : 'hello React.js'
    }
  }

  destory = () => {
    ReactDOM.unmountComponentAtNode( document.querySelector('#root') ) //必须是root
  }
  render () {
    return (
      <Fragment>
        <div className = "father-box">

          <h3> Father组件 - 更新阶段</h3>
          <button onClick = { this.destory }> 内部销毁 </button>
          <p> constructor : { this.state.msg } </p>
          <p> money: { this.props.money } </p>
        </div>
      </Fragment>
    )
  }

  componentWillUnmount () {
    /* 
      组件销毁
        外部销毁: 开关 【推荐】
        内部销毁: 
          ReactDOM.unmountComponentAtNode( document.querySelector('#root') ) //必须是root

    */
    console.log( 'componentWillUnmount' )
  }

}

export default Father   

外部销毁

import React from 'react';
import logo from './logo.svg';
import './App.css';
import Father from './components/Father'

class App extends React.Component {
  constructor () {
    super()
    this.state = {
      money: 1000,
      flag: true
    }
  }

  changeMoney = () => {
    this.setState({
      money: 2000
    })
  }

  changeFlag = () => {
    this.setState({
      flag: !this.state.flag
    })
  }

  render () {
    const { money,flag } = this.state 
    return (
      <div className="App">
        <h3> 组件的生命周期 </h3>
        <button onClick = { this.changeMoney }> changeMoney </button>
        <button onClick = { this.changeFlag }> Father-重建-销毁 </button>
        {flag && <Father money = { money }></Father>}
      </div>
    );
  }
}

export default App;
12.componentDidCatch(error, info)

错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。

如果类组件定义了此生命周期方法,则它将成错误边界。在它中调用setState()可以让你在下面的树中捕获未处理的JavaScript错误,并显示一个后备UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。

错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。

import React from 'react'

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: true };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    console.log( 'getDerivedStateFromError ')
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    // logComponentStackToMyService(info.componentStack);
    console.log( 'info',info )
  }

  changeHasError = () => {
    this.setState({
      hasError: !this.state.hasError
    })
  }

  render() {

    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return (
        <div>
          <h1>Something went wrong.</h1>
          <button onClick = { this.changeHasError }> 点击 </button>
        </div>
      );
    }

    return <div>  其他子组件正常  </div>; 
  }
}

export default ErrorBoundary

PureComponent

PureComponnet里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate,而且。是否重新渲染以shouldComponentUpdate的返回值为最终的决定因素。

import React, { PureComponent } from 'react'

class YourComponent extends PureComponent {
  ……
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值