React 组件生命周期

本文使用的是最新版本的react(16.3.2), 以及react-router(4.2.0)

React Router被拆分成三个包:react-router, react-router-dom, react-router-native

先了解一个简单的东西,组件在ES6和ES5的不同写法:

//ES6
class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ES5:
React.createClass({
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
})


生命周期例子

下面的例子中会使用ES6的写法去学习组件生命周期。下面例子的源码

给大家看一下整个用于验证的界面。


//index.js
import React from 'react';
import ReactDOM from 'react-dom'
import './index.css';
import App from "./App";
import {BrowserRouter} from 'react-router-dom'

ReactDOM.render((
    <BrowserRouter>
        <App />
    </BrowserRouter>
), document.getElementById('root'))
//App.js
import React, {Component} from 'react';
import './App.css';
import Link from "react-router-dom/es/Link";
import About from "./containers/about";
import Inbox from "./containers/inbox";
import Switch from "react-router-dom/es/Switch";
import Route from "react-router-dom/es/Route";

class App extends Component {
    render() {
        return (
            <div>
                <h1>App</h1>
                <ul>
                    <li><Link to="/About">About</Link></li>
                    <li><Link to="/Inbox">Inbox</Link></li>
                    <li><Link to="/Messages/2">Message</Link></li>
                </ul>

                <main>
                    <Switch>
                        <Route path="/About" component={About}/>
                        <Route path="/Inbox" component={Inbox}/>
                    </Switch>
                </main>
            </div>
        )
    }
}

export default App;
//inbox.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import Message from "./message";

class Inbox extends Component {
    constructor(props) {
        super(props);
        this.state = {
            num: Math.random() * 100
        };
    }

    shouldComponentUpdate() {
        console.log("parent shouldComponentUpdate");
        return true;        // 记得要返回true
    }

    propsChange() {
        console.info("更新父组件state");
        this.setState({
            num: Math.random() * 100
        });
    }

    setLifeCycleState() {
        console.info("更新子组件state");
        this.refs.rLifeCycle.setTheState();
    }

    forceLifeCycleUpdate() {
        console.info("强制更新子组件");
        this.refs.rLifeCycle.forceItUpdate();
    }

    parentForceUpdate() {
        console.info("强制更新父组件");
        this.forceUpdate();
    }

    render() {
        console.log("parent render")

        return (
            <div>
                <a href="javascript:void(0);" className="weui_btn weui_btn_primary" onClick={this.propsChange.bind(this)}>更新父组件state</a><br/>
                <a href="javascript:void(0);" className="weui_btn weui_btn_primary" onClick={this.setLifeCycleState.bind(this)}>更新子组件state</a><br/>
                <a href="javascript:void(0);" className="weui_btn weui_btn_primary" onClick={this.forceLifeCycleUpdate.bind(this)}>forceUpdate 子组件</a><br/>
                <a href="javascript:void(0);" className="weui_btn weui_btn_primary" onClick={this.parentForceUpdate.bind(this)}>forceUpdate 父组件</a>
                <Message ref="rLifeCycle" num={this.state.num}></Message>
            </div>
        );
    }
}

export default Inbox;
//message.js
import React, {Component} from 'react';

class Message extends Component {
    constructor(props) {
        super(props);
        console.log("constructor");
        this.state = {str: "hello"};
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        console.log("getDerivedStateFromProps");
        return {str: "getDerivedStateFromProps update state"};
    }

    // UNSAFE_componentWillMount() {
    //     console.log("UNSAFE_componentWillMount()");
    // }

    componentDidMount() {
        console.log("componentDidMount");
    }

    shouldComponentUpdate() {
        console.log("shouldComponentUpdate");
        return true;        // 记得要返回true
    }

    getSnapshotBeforeUpdate() {
        console.log("getSnapshotBeforeUpdate");
        return true;
    }

    // componentWillUpdate() {
    //     console.log("componentWillUpdate");
    // }

    componentDidUpdate() {
        console.log("componentDidUpdate");
    }

    componentWillUnmount() {
        console.log("componentWillUnmount");
    }

    setTheState() {
        let s = "hello";
        if (this.state.str === s) {
            s = "HELLO";
        }
        this.setState({
            str: s
        });
    }

    forceItUpdate() {
        this.forceUpdate();
    }

    render() {
        console.log("render");
        return (
            <div>
                <span>Props:<h2>{this.props.num}</h2></span>
                <br/>
                <span>State:<h2>{this.state.str}</h2></span>
            </div>
        );
    }
}

export default Message;
//about.js
import React, { Component } from 'react';

class About extends Component {
    render() {
        return (
            <div>About</div>
        );
    }
}

export default About;

装配(第一次渲染成功)

constructor -> getDerivedStateFromProps -> UNSAFE_componentWillMount(不能与getDerivedStateFromProps共存) -> render -> componentDidMount


constructor()

React组件的构造函数将会在装配之前被调用。当为一个React.Component子类定义构造函数时,你应该在任何其他的表达式之前调用super(props)。否则,this.props在构造函数中将是未定义,并可能引发异常。

* 看一下解析出来的源码好了
class Message extends Component {
    constructor(props) {
        super(props);
        console.log("Message constructor");
    }

    ....
}
装换成ES5的代码为
var Message = function(_Component) {
    _inherits(Message, _Component); //将Message的prototype 指向给ReactComponent.prototype。但是构造函数会重写为下面的Message方法。

    function Message(props) {
        _classCallCheck(this, Message); //判断当前的对象是不是使用Message方法new 出来的实例。
        var _this = _possibleConstructorReturn(this, (Message.__proto__ || Object.getPrototypeOf(Message)).call(this, props)); //对应super(props)
        console.log("Message constructor");
        return _this;
    }
}

执行下面这行代码之前我们看一下,当前this,如下图:


var _this = _possibleConstructorReturn(this, (Message.__proto__ || Object.getPrototypeOf(Message)).call(this, props))
执行完上面这行代码之后是为了形成this对象,如下图,这也是为什么要执行super(props)的目的。

构造函数是初始化状态的合适位置。若你不初始化状态且不绑定方法,那你也不需要为你的React组件定义一个构造函数。
但是一般情况下我们会在contructor中初始化我们的state。
constructor(props) {
  super(props);
  this.state = {
    color: props.initialColor //提示: props不会被在当前组件被修改,除非是父组件重新渲染传入新的props。
  };
}

contructor什么时候会调用?

组件装配之前,我的理解就是初始化这个组件,但是在组件的生命周期,当父组件state变化导致render的前提下,一般情况下构造函数不会执行,除非当前组件的key发生了变化。如下:

<Message ref="rLifeCycle" key={Math.random()} num={this.state.num}></Message>
基本上每次父组件传入进来的key都不同,导致会创建新的组件。

static getDerivedStateFromProps()

前身:componentWillReceiveProps / UNSAFE_componentWillReceiveProps()

如果定义了getDerivedStateFromProps后,又定义了componentWillReceiveProps。那么,只有前者会被调用,并且你会收到一个警告。

static getDerivedStateFromProps(nextProps, prevState)
在装配了的组件接收到新属性前调用。若你需要更新状态响应属性改变(例如,重置它),你可能需对比this.props和prevState并在该方法中返回一个对象来更新状态,或者返回null来表明新属性不需要更新任何状态。这里返回的更细状态就是以前this.setState(object)中的object。

static getDerivedStateFromProps(nextProps, prevState) {
  if(nextProps.currentRow === prevState.lastRow) {
    return null;
  }
 
  return {
    lastRow: nextProps.currentRow,
    isCrollingDown: nextProps.curentRow > prevState.lastRow
  }
}

UNSAFE_componentWillMount()

* componentWillMount / UNSAFE_componentWillMount 可以用到React 16.4。在React 17里将被彻底移除。

UNSAFE_componentWillMount()在装配发生前被立刻调用。其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲。

避免在该方法中引入任何的副作用或订阅。对于这些使用场景,我们推荐使用constructor()来替代。

这是唯一的会在服务端渲染调起的生命周期钩子函数。

* 如果和上面的getDerivedStateFromProps同时存在会报错:

index.js:2178 Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
Message uses getDerivedStateFromProps() but also contains the following legacy lifecycles:
  UNSAFE_componentWillMount

The above lifecycles should be removed. Learn more about this warning here:
我在网上看到: componentWillMount--使用componentDidMount代替,其实看到这里真的有疑问说为啥能代替?一个发生在render前,一个发生在render后。如果存在设置state,会有两次渲染和一次渲染的区别。真的不懂这句话的含义,望有知道的告知。

render()

render()方法是必须的。

当被调用时,其应该检查this.props 和 this.state并返回以下类型中的一个:

React元素。 通常是由 JSX 创建。该元素可能是一个原生DOM组件的表示,如<div />,或者是一个你定义的合成组件。

字符串和数字。 这些将被渲染为 DOM 中的 text node。

Portals。 由 ReactDOM.createPortal 创建。

null。 什么都不渲染。

布尔值。 什么都不渲染。(通常存在于 return test && <Child />写法,其中 test 是布尔值。)

当返回null 或 false时,ReactDOM.findDOMNode(this) 将返回 null。

render()函数应该纯净,意味着其不应该改变组件的状态,其每次调用都应返回相同的结果,同时不直接和浏览器交互。若需要和浏览器交互,将任务放在componentDidMount()阶段或其他的生命周期方法。保持render() 方法纯净使得组件更容易思考。

* 若 shouldComponentUpdate()返回false,render()函数将不会被调用

componentDidMount()

在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。若你需要从远端加载数据,这是一个适合实现网络请求的地方。在该方法里设置状态将会触发重渲。

这一方法是一个发起任何订阅的好地方。如果你这么做了,别忘了在componentWillUnmount()退订。

在这个方法中调用setState()将会触发一次额外的渲染,但是它将在浏览器刷新屏幕之前发生。这保证了即使render()将会调用两次,但用户不会看到中间状态。谨慎使用这一模式,因为它常导致性能问题。然而,它对于像模态框和工具提示框这样的例子是必须的。这时,在渲染依赖DOM节点的尺寸或者位置的视图前,你需要先测量这些节点。



更新状态

getDerivedStateFromProps -> shouldComponentUpdate -> componentWillUpdate(与getDerivedStateFromProps & getSnapshotBeforeUpdate 不能共存) -> render -> getSnapshotBeforeUpdate -> componentDidUpdate


shouldComponentUpdate(nextProps, nextState)

使用shouldComponentUpdate()以让React知道当前状态或属性的改变是否不影响组件的输出。默认行为是在每一次状态的改变重渲,在大部分情况下你应该依赖于默认行为。
当接收到新属性或状态时,shouldComponentUpdate() 在渲染前被调用。默认为true。该方法并不会在初始化渲染或当使用forceUpdate()时被调用。

当他们状态改变时,返回false 并不能阻止子组件重渲。

当前,若shouldComponentUpdate()返回false,而后UNSAFE_componentWillUpdate(),render(), 和 componentDidUpdate()将不会被调用。注意,在未来React可能会将shouldComponentUpdate()作为一个线索而不是一个严格指令,返回false可能仍然使得组件重渲。

意思为当父子组件存在,父组件当前函数返回值为false的时候,当前组件的state变化也不会引起重新渲染。但是不会影响子组件的state变化导致的子组件的重新渲染。


UNSAFE_componentWillUpdate(nextProps, nextState)

当接收到新属性或状态时,UNSAFE_componentWillUpdate()为在渲染前被立即调用。在更新发生前,使用该方法是一次准备机会。该方法不会在初始化渲染时调用。

*注意你不能在这调用this.setState(),若你需要更新状态响应属性的调整,使用getDerivedStateFromProps() 代替。
*这一生命周期之前叫做componentWillUpdate。这一名字在17版前都有效。可以使用rename-unsafe-lifecycles codemod来自动更新你的组件。
*若shouldComponentUpdate()返回false,UNSAFE_componentWillUpdate()将不会被调用。
*与getDerivedStateFromProps & getSnapshotBeforeUpdate 不能共存,同时存在会报错。


getSnapshotBeforeUpdate()

在最新的渲染输出提交给DOM前将会立即调用。它让你的组件能在当前的值可能要改变前获得它们。这一生命周期返回的任何值将会 作为参数被传递给componentDidUpdate()。

componentDidUpdate(prevProps, prevState)

会在更新发生后立即被调用。该方法并不会在初始化渲染时调用。

当组件被更新时,使用该方法是操作DOM的一次机会。这也是一个适合发送请求的地方,要是你对比了当前属性和之前属性(例如,如果属性没有改变那么请求也就没必要了)。

*若shouldComponentUpdate()返回false,componentDidUpdate()将不会被调用。


卸载状态

componentWillUnmount()

在组件被卸载和销毁之前立刻调用。可以在该方法里处理任何必要的清理工作,例如解绑定时器,取消网络请求,清理任何在componentDidMount环节创建的DOM元素。

强制重渲染 

componentWillUpdate(与后面getSnapshotBeforeUpdate不能共存) -> render -> getSnapshotBeforeUpdate -> componentDidUpdate


component.forceUpdate(callback)

默认情况,当你的组件或状态发生改变,你的组件将会重渲。若你的render()方法依赖其他数据,你可以通过调用forceUpdate()来告诉React组件需要重渲。

调用forceUpdate()将会导致组件的 render()方法被调用,并忽略shouldComponentUpdate()。这将会触发每一个子组件的生命周期方法,涵盖,每个子组件的shouldComponentUpdate() 方法。若当标签改变,React仅会更新DOM。

通常你应该尝试避免所有forceUpdate() 的用法并仅在render()函数里从this.props和this.state读取数据。


错误处理状态

componentDidCatch(error, info)

错误边界是React组件,并不是损坏的组件树。错误边界捕捉发生在子组件树中任意地方的JavaScript错误,打印错误日志,并且显示回退的用户界面。错误边界捕捉渲染期间、在生命周期方法中和在它们之下整棵树的构造函数中的错误。

如果定义了这一生命周期方法,一个类组件将成为一个错误边界。在错误边界中调用setState()让你捕捉当前树之下未处理的JavaScript错误,并显示回退的用户界面。只使用错误边界来恢复异常,而不要尝试将它们用于控制流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值