React 0基础学习路线(5)— 图解详述生命周期原理详述(附详细案例代码解析过程)

1. 重点提炼

  • react 组件生命周期
  • 分类
    • 挂载阶段
      • constructor(组件初始化:组件内部数据及状态的初始化,及事件相关的初始化)
      • static getDerivedStateFromProps(对props和state进行处理,当两者发生变化的时候,我们希望通过这个生命周期产生一个新的state,作用:对state作二次处理,要么根据state的变化进行处理,要么根据props的变化进行处理。大部分的情形都是通过props的变化去更新state)
      • render(无论是挂载阶段还是更新阶段,都会发生的生命周期,主要用来做主件渲染)
      • componentDidMount(组件解析完成后,包括把组件渲染到页面当中,即虚拟dom解析成真实dom结构后,所执行的函数)
    • 更新阶段
      • static getDerivedStateFromProps
      • shouldComponentUpdate(确定当前是否允许调用render,根据其返回的bool值来决定是否调用后面的render,主要对组件的渲染逻辑进行一定的优化)
      • render
      • getSnapshotBeforeUpdate(主件渲染完成后,准备挂载页面的时候,发生该生命周期,主要获取上一次的dom(结构)快照)
      • componentDidUpdate(更新完成)
    • 销毁阶段
      • componentWillUnmount(当组件被卸载,可对组件内部与组件没有关系的一些内部数据进行清理,如定时器)
    • 错误
      • getDerivedStateFromError(补获子组件发生的错误,一般作为容器组件存在,即父组件捕获,显示一个错误页面)
      • componentDidCatch(提交一个详细的错误报告!发生邮件给管理员或运营人员。)

2. React.js 组件之生命周期

其实很多库、框架,都会有很多回调函数。

如某个动画框架:类似是以下函数,有开始、结束、移动等阶段,我们可以通过回调函数传入来扩展各个阶段的功能(这里的不同阶段,就可以理解为生命周期了)。

    animate({
        // ...设置动画的属性
        onstart() {
 
        },
        onend() {
 
        },
        onmove() {
 
        }
    })

2.1 生命周期

所谓的生命周期就是指某个事物从开始到结束的各个阶段,当然在 React.js 中指的是组件从创建到销毁的过程,React.js 在这个过程中的不同阶段调用的函数,通过这些函数,我们可以更加精确的对组件进行控制,前面我们一直在使用的 render 函数其实就是组件生命周期渲染阶段执行的函数,即生命周期中的某一个阶段调用的。

2.1.1 周期分类

React.js 为组件的生命周期划分了四个不同的阶段

  • 挂载阶段(从创建到显示在页面的这个过程)
  • 更新阶段(这个组件已经在页面中存在,但是因为某些东西的变化,比如状态的变化、传入参数发生变化都会导致这个组件重新渲染,我们称之为更新阶段)
  • 卸载阶段(我们将组件从页面中移除了)
  • 错误处理

不同的阶段又会对应着一些不同的函数(每一个周期,都可能调用不同的函数)

参考:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

生成期到存在期,这一阶段就是挂载阶段,从它创建到出现到页面当中,会有不同的函数参与,会有很多函数被调用,其中有些函数,我们是可以进行编程的。就相当于一个类,这个组件类中会有很多方法,有些特殊方法会主动去执行(调用),这个时候我们可以为这个类去写这个方法,从而好好地控制组件。

一个组件的第一个生命周期,调用的函数就是constructor(类的初始化构造函数),然后在挂载的时候,即将显示在页面之前,在render渲染页面之前会调用componentWillMount方法(这个函数是在被渲染之前调用的)。

当这个函数执行完毕后紧接着调用render函数,它的作用就是将jsx编译成虚拟dom,然后再将虚拟dom生成最终的html,即真实的dom元素,这个就是render函数执行的过程。整个渲染完成后就会调用componentDidMount方法,它称之为“挂载完成”,这个函数执行完代表当前我们的组件已经出现在htmldom结构当中了,即完成了完整的渲染。

这个过程完成后,我们在浏览器上基本就能够看到我们的组件了,组件就会出现在页面当中。紧接着,这个组件就在页面上等待着用户或者数据与它进行交互。在这个过程中,如果组件当中相关的数据发生了改变,状态发生了改变,传入的组件参数发生了改变等,它都会导致另外一个过程的发生。这个过程我们称之为更新

组件当它更新的时候,你会看到,如果是组件本身自己的state(私有数据)发生变化,它会调用shouldComponentUpdate函数,这个方法其实是早期的函数,目前它已经被修改为另一个函数了(一会再去了解)。(注意:stateprops的更新,调用的是不同的生命周期函数。)如果是props更新,它会调用componentWillReceiveProps函数,但不管哪种方式它都会进入下一个阶段,componentWillUpdate函数,紧接着又会触发render,触发完成之后,就会将组件重新进行渲染,整个渲染完成后就会调用componentDidUpdate方法。

并且还有一个注销阶段,即主件在页面中被删除了,这个时候就会触发componentWillUnMount函数。

这整个过程只是作为参考,因为有些细节已经发生了变化(随着不断地更新)。最新大家了解到的叫react hooks(钩子),它的作用与我们讲的这个生命周期过程是类似的。我们需要学习这些生命周期,在什么情况下去执行,然后我们可以在这些阶段中做哪些事情,以及基本的应用场景,通过案例学习常用的生命周期。

life cycle

2.1.2 挂载阶段

挂载阶段是指组件创建到渲染到页面的过程,这个过程提供了四个不同的函数

  • constructor() (对象构造函数/初始化函数,同时也是类主件)
  • render()
  • static getDerivedStateFromProps()
  • componentDidMount()

注意这里没有componentWillMount(),以及更新阶段的componentWillReceiveProps函数和componentWillUpdate函数,因为这是原先的,现在都被static getDerivedStateFromProps()取代了。

2.1.2.1 constructor

constructor(props)

类的构造函数,也是组件初始化函数,一般情况下,我们会在这个阶段做一些初始化的工作(如:对组件内部行为状态进行初始化)

  • 初始化 state
  • 处理事件绑定函数的 this

2.1.2.2 render()

render 方法是 Class 组件必须实现的方法

2.1.2.2.1 example01

src/App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
    
    constructor(props) {
        super(props);
    }
 
    render() {
        return (
            <div className="App">
                <LifeCycleDemo/>
            </div>
        )
    }
 
}
 
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
    
    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);
        console.log('组件初始化');
    }
 
    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
            </div>
        );
    }
}
image-20200604173818107

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.01
Branch:branch3

commit description:v2.01-example01(生命周期-constructor)

tag:v2.01

2.1.2.3 static getDerivedStateFromProps()

static getDerivedStateFromProps(props, state)

该方法会在 render 方法之前调用,无论是挂载阶段还是更新阶段,它的存在只有一个目的:让组件在 props 变化时更新 state

更多细节可以上官方网站检索:

image-20200604174752085

static getDerivedStateFromProps(props, state) 该函数也是可以接收参数的。我们打印参数看看。

2.1.2.3.1 expample02

react-Novice04\app\src\components\LifeCycleDemo.js

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('组件初始化');
    }

    static getDerivedStateFromProps(props, state) {
        // 从props中获取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }

    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
            </div>
        );
    }
}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1
        }
    }

    render() {
        return (
            <div className="App">
                <LifeCycleDemo val={this.state.pVal}/>
            </div>
        )
    }
}

export default App;

打印了两个对象,第一个对象其实就是props,第二个对象就是state

这个函数的作用是从props上更新state

/**
* 组件的数据(状态)来源来自:
 *  - 传入 props
 *  - 内部 state
*
* 当一个组件拥有一个自己的状态(state),同时这个状态又依赖 props 的变化的时候
*
*/

image-20200704141834021

先不用管警告。getDerivedStateFromProps发生在组件渲染之前。

image-20200704140339477

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.02
Branch:branch3

commit description:v2.02-example02(生命周期-getDerivedStateFromProps)

tag:v2.02

2.1.2.4 componentDidMount()

componentDidMount()

在组件挂载后(render 的内容插入 DOM 树中)调用。通常在这个阶段,我们可以:

  • 操作 DOM 节点(如获取元素宽高等,建议在这个生命周期做此操作,前面的函数中做是有风险的!)
  • 发送请求(如:ajax请求、异步请求)
2.1.2.4.1 example03

组件挂载完成。

react-Novice04\app\src\components\LifeCycleDemo.js

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('组件初始化');
    }

    static getDerivedStateFromProps(props, state) {
        // 从props中获取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }

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

    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
            </div>
        );
    }
}

App.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
export default App;

image-20200604192132454

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.03
Branch:branch3

commit description:v2.03-example03(生命周期-componentDidMount)

tag:v2.03

2.1.2.4.2 example04

深入探究getDerivedStateFromProps和组件渲染。

2.1.2.4.2.1 example04-1

更新子组件的state,就会触发getDerivedStateFromProps方法,子组件自己的状态变化就会导致当前的子组件重新渲染。

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('组件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 从props中获取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    // 更新子组件的state,就会触发getDerivedStateFromProps方法,子组件自己的状态变化就会导致当前的子组件重新渲染
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

image-20200604193321186

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-1
Branch:branch3

commit description:v2.04-1-example04-1(深入探究getDerivedStateFromProps和组件渲染,子组件自己的状态变化就会导致当前的子组件重新渲染。)

tag:v2.04-1

2.1.2.4.2.2 example04-2

同时我们父级也设置一个按钮!

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal: this.state.pVal + 1
                    })
                }}>父组件的按钮</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('组件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 从props中获取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    // 更新子组件的state,就会触发getDerivedStateFromProps方法,子组件自己的状态变化就会导致当前的子组件重新渲染. 同时我们父级也设置一个按钮!
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

当子组件的数据变化,它会更新与数据挂钩的组件,即更新子组件,刷新子组件页面。

父组件的数据变化,也会导致子组件也会更新,从这里就能看见不一样的东西。getDerivedStateFromProps函数在组件渲染的之前,即render方法调用之前,再调用一遍。

即渲染函数会在组件自身的 state 变化的时候,以及父组件更新的时候执行。

实际上数据变了,页面的组件就一定会发生变化,包括其子组件,如果子组件依赖的数据也发生了变化,这个依赖的数据包括它自身的和父级传进去的数据。

注意:一个组件中props和state的变化,都会导致组件的重新渲染。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-2
Branch:branch3

commit description:v2.04-2-example04-2(深入探究getDerivedStateFromProps和组件渲染,父组件自己的状态变化就会导致当前的子组件重新渲染。)

tag:v2.04-2

2.1.2.4.2.3 example04-3

如果子组件不更新呢?

修改:再新增一个属性,点击事件会改变该属性。

app.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                    })
                }}>父组件的按钮</button>
                <hr/>
 
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
 
export default App;

点击父组件的按钮,也会执行子组件的getDerivedStateFromProps函数,那最终会调用其render吗?

实际上是执行了,但是我们发现调用render,但并不代表整个页面的dom都会更新。

实际上没有将render的返回值,拿去覆盖原先的dom。虽然执行了render,但并没有更新dom

一定要注意:render执行,一定不代表渲染了整个页面(生成html,替换原先页面上的html)。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-3
Branch:branch3

commit description:v2.04-3-example04-3(深入探究getDerivedStateFromProps和组件渲染,组件实际不刷新原理。)

tag:v2.04-3

2.1.2.4.2.4 example04-4

src/components/LifeCycleDemo.js

如添加一个input

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('组件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 从props中获取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    // 更新子组件的state,就会触发getDerivedStateFromProps方法,子组件自己的状态变化就会导致当前的子组件重新渲染. 同时我们父级也设置一个按钮!
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <input type="text"/>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

我们在input中输入值,点击按钮,发现页面并没有刷新(因为input的值始终存在)。实际上React会去比较页面,如果没有任何变化,它是不会刷新页面的。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-4
Branch:branch3

commit description:v2.04-4-example04-4(深入探究getDerivedStateFromProps和组件渲染,组件实际不刷新原理。加入input。)

tag:v2.04-4

2.1.3 案例:邮件发送-收件人选择

我们会发现一种状态:

当一个组件拥有一个自己的状态(state),同时这个状态又依赖 props 的变化的时候。

通过以下的例子说明。

案例:[邮件发送-收件人选择]

仿 QQ 邮箱发送邮件的添加接收人功能

2.1.3.1 example05

分析需求,可以把这页面细分为收件人组件,通讯录(好友列表)组件共同组成这个页面,紧接着我们在收件人里可以输入邮箱,这是一个数据,我们可以将其放入state(私有状态)里,即在state里存在一个属性可以存放收件人数据(由用户输入)。(关于样式去可参考小迪的源码,也可以自己写!)

image-20200704143547899

2.1.3.1.1 example05-1

基本框子实现。

src/App.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
import Mail from "./components/Mail";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                
                <hr />
                <Mail />
            </div>
        )
    }
 
}
 
export default App;

src/components/mail.css

ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
}
li {
    line-height: 30px;
}
.fl {
    float: left;
}
.fr {
    float: right;
}
.clear:after {
    content: '';
    display: block;
    clear: both;
}
 
.box {
    padding: 10px;
    margin: 10px;
    border: 1px solid #000;
    width: 200px;
}
 
.multi-input {
    margin: 10px;
    padding: 5px;
    border: 1px solid #666;
}
.multi-input>div {
    height: 30px;
}

src/components/Mail.js

import React from 'react';
 
import SendList from './SendList';  // 发送列表组件
import FriendList from './FriendList';  // 好友列表组件
import './mail.css';
 
export default class Mail extends React.Component{
 
    constructor(props) {
        super(props);
    }
 
    render() {
        return(
            <div>
 
                <div className="clear">
                    <h1>发送邮件</h1>
                    <hr/>
                    <div className="fl">
                        <SendList />
                    </div>
 
                    <div className="fr">
                        <FriendList />
                    </div>
                </div>
            </div>
        )
    }
}

src/components/FriendList.js

import React from 'react';
 
 
export default class FriendList extends React.Component{
 
    render() {
        return(
            <div>
                FriendList
            </div>
        )
    }
 
}

src/components/SendList.js

import React from 'react';
 
 
export default class SendList extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '张三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };
 
    }
 
    render() {
        return(
            <div>
 
                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" />
                    </div>
                </div>
 
            </div>
        )
    }
 
}

image-20200605114405342

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-1
Branch:branch3

commit description:v2.05-1-example05-1(案例:[邮件发送-收件人选择]——基本框子实现)

tag:v2.05-1

2.1.3.1.2 example05-2

在当前这个组件的内部会维护一个收件人的列表,我们可以通过这个组件的input输入框来新增收件人。

当前这个组件的users 会根据传入的 props来新增数据,当前组件的 state 依赖了 props的数据进行更新。

如何拿到input里的数据?

1、事件源e.target

2、通过ref

3、通过受控组件

事件源e.target

可能大家会这样处理,但是this.state.users.push()返回的是新数组的长度,小迪也确实犯了这个错。

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '张三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser({target:{value}}) {
        this.setState({
            users: this.state.users.push()
        })
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

我们可以用解构的方法去做。

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '张三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser({target:{value}}) {
        this.setState({
            // users: this.state.users.push()
            users: [...this.state.users, {email:value}]
        })
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

发现还是有问题,怎么写一个就又来一个!!!我们应该判断一下键盘的截止符(回车)。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-2
Branch:branch3

commit description:v2.05-2-example05-2(案例:[邮件发送-收件人选择]——事件源e.target 来获取input数据,但存在问题。)

tag:v2.05-2

2.1.3.1.3 example05-3

src/components/SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '张三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 经典错误,push返回的不是数组,而是数组长度。
                // ...this.state.users解构
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-3
Branch:branch3

commit description:v2.05-3-example05-3(案例:[邮件发送-收件人选择]——事件源e.target 来获取input数据)

tag:v2.05-3

2.1.3.1.4 example05-4

组件相互交互:通过点击好友列表组件后,添加好友到发送列表组件中。并且收件人自己也可通过编辑添加收件人。

他俩非嵌套关系,因此不能通过props进行传递,可以通知父级(使用回调函数),让父级传递。

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <hr />
                <Mail />
            </div>
        )
    }
}

export default App;

react-Novice04\app\src\components\Mail.js

import React from 'react';

import SendList from './SendList';  // 发送列表组件
import FriendList from './FriendList';  // 好友列表组件
import './mail.css';

export default class Mail extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            friend: null
        };

        this.clickUser = this.clickUser.bind(this);
    }

    clickUser(friend) {
        // 子级点击选项,触发点击事件,紧接着执行父级回调函数clickUser
        console.log(friend);
    }

    render() {
        return(
            <div>

                <div className="clear">
                    <h1>发送邮件</h1>
                    <hr/>

                    <div className="fl">
                        {/*发送接收来的好友列表点击的好友信息,进行转发*/}
                        <SendList friend={this.state.friend} />
                    </div>

                    <div className="fr">
                        {/*接收好友列表点击的好友*/}
                        {/*像事件绑定一样,把clickUser函数传给子级的onClickUser属性,子组件一点击,就触发onClickUser的函数*/}
                        <FriendList onClickUser={this.clickUser} />
                    </div>
                </div>
            </div>
        )
    }
}

react-Novice04\app\src\components\FriendList.js

import React from 'react';


export default class FriendList extends React.Component{

    constructor(...props) {
        super(...props);

        this.state = {
            friends: [
                {id: 1, name: '张三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.clickUser = this.clickUser.bind(this);

    }

    clickUser(friend) {
        // 判断父级有没有传来一个回调函数
        if ('function' === typeof this.props.onClickUser) {
            this.props.onClickUser(friend);
        }
    }

    render() {
        return(
            <div>
                <ul className="box fl">
                    {
                        // 注意不要 onClick={this.clickUser(friend)} ,这样设置不用点击,默认就调用了,传参一般需要放入嵌套的函数里
                        this.state.friends.map(friend => (
                            <li key={friend.id} onClick={() => {
                                this.clickUser(friend)
                            }}>
                                {friend.name}
                            </li>
                        ))
                    }
                </ul>

            </div>
        )
    }

}

需求:在当前这个组件的内部会维护一个收件人的列表,我们可以通过这个组件的input输入框来新增收件人

当前这个组件的 users 会根据传入的 props来新增数据,当前组件的 state 依赖了 props 的数据进行更新

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        /**
         * 在当前这个组件的内部会维护一个收件人的列表
         * 我们可以通过这个组件的input输入框来新增收件人
         *
         * 当前这个组件的 users 会根据传入的 props 来新增数据
         * 当前组件的 state 依赖了 props 的数据进行更新
         */

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                // {id: 1, name: '张三', email: '[email protected]'},
                // {id: 2, name: '李四', email: '[email protected]'},
                // {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 经典错误,push返回的不是数组,而是数组长度。
                // ...this.state.users解构
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }
}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-4
Branch:branch3

commit description:v2.05-4-example05-4(案例:[邮件发送-收件人选择]——父级设置回调函数,子级借助该函数传递用户点击好友的信息)

tag:v2.05-4

2.1.3.1.5 example05-5

src/components/Mail.js

import React from 'react';
 
import SendList from './SendList';  // 发送列表组件
import FriendList from './FriendList';  // 好友列表组件
import './mail.css';
 
export default class Mail extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            friend: null
        };
 
        this.clickUser = this.clickUser.bind(this);
    }
 
    clickUser(friend) {
        // console.log(friend);
        // 更新父组件的好友列表
        this.setState({
            friend
        })
    }
 
    render() {
        return(
            <div>
 
                <div className="clear">
                    <h1>发送邮件</h1>
                    <hr/>
 
                    <div className="fl">
                        {/*发送接收来的好友列表点击的好友*/}
                        <SendList friend={this.state.friend} />
                    </div>
 
                    <div className="fr">
                        {/*接收好友列表点击的好友*/}
                        <FriendList onClickUser={this.clickUser} />
                    </div>
                </div>
 
            </div>
        )
    }
}

当前这个组件的 users会根据传入的props 来新增数据,,且当前组件的 state依赖了 props 的数据进行更新

实现:通过生命周期函数

getDerivedStateFromProps会在调用 render 方法之前调用,(拿到实时的stateprops)并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

注意:state 的值在任何时候都取决于props,即state依赖于props,但不是等同关系,实际两者一样,就没必要设置state了(数据还依赖于外界传入,设置state就没任何意义),并且官方推荐使用props,能不用state就不用。除非state依赖于props,但又受自己内在影响。

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        /**
         * 在当前这个组件的内部会维护一个收件人的列表
         * 我们可以通过这个组件的input输入框来新增收件人
         *
         * 当前这个组件的 users 会根据传入的 props 来新增数据
         * 当前组件的 state 依赖了 props 的数据进行更新
         */

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                // {id: 1, name: '张三', email: '[email protected]'},
                // {id: 2, name: '李四', email: '[email protected]'},
                // {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    static getDerivedStateFromProps(props, state) {
        console.log(props, state);
        if (props.friend) {
            // 如果props传入了friend,则把传入的friend追加到state中(state依赖于外部props)
            return {
                users: [...state.users, props.friend]
            }
        }
        return null;
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 经典错误,push返回的不是数组,而是数组长度。
                // ...this.state.users解构
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

最后报错,是因为相同的key。我们从好友列表添加完后,再手动添加收件人,之前从好友列表添加的数据又会出现一遍!当state、props更新,getDerivedStateFromProps都会执行。我们手动添加的时候调用addUser函数,其中又会执行this.setState函数,这里就是更新state了,导致getDerivedStateFromProps又执行一遍,导致最开始的好友列表点击的好友重复追加了!如何解决呢?我们应该判断当前数据是否存在,即是否允许添加!

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-5
Branch:branch3

commit description:v2.05-5-example05-5(案例:[邮件发送-收件人选择]——父级设置回调函数,子级借助该函数传递用户点击好友的信息,出现重复可以的问题)

tag:v2.05-5

2.1.3.1.6 example05-6

注意:静态方法是属于类(构造函数的),而不是对象,所以不能在静态方法中使用 this 关键字

state、props更新,它都会执行。

getDerivedStateFromProps(props, state)作用:它的返回值将是新的state,本质上说就是通过props来更新state

src/components/SendList.js

import React from 'react';
 
 
export default class SendList extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
            ]
        };
 
        this.addUser = this.addUser.bind(this);
    }
 
    static getDerivedStateFromProps(props, state) {
        console.log(props, state);
        if (props.friend) {
 
            return {
                // getNewUsers必须设置为静态方法,因为静态方法只能调用静态方法,不能使用this
                users: SendList.getNewUsers(props.friend, state.users)
            };
        }
        return null;
    }
 
    // 如何拿到input里的数据?1、事件源e.target 2、通过ref 3、通过受控组件
    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 经典错误,push返回的不是数组,而是数组长度。
                // ...this.state.users解构
                users: SendList.getNewUsers({email: e.target.value}, this.state.users)
            });
            // 清空提交后的值,下次则再重新输入E-mail
            e.target.value = '';
        }
    }
 
    // 看用户是否存在,如果存在就不再添加了
    static getNewUsers(user, users) {
        if ( users.find(u => u.email === user.email) ) {
            return [...users];
        } else {
            return [...users, user];
        }
    }
 
    render() {
        // console.log(this.state.users)
        return(
            <div>
 
                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>
 
            </div>
        )
    }
 
}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-6
Branch:branch3

commit description:v2.05-6-example05-6(案例:[邮件发送-收件人选择]——父级设置回调函数,子级借助该函数传递用户点击好友的信息,完成)

tag:v2.05-6

2.1.4 更新阶段

更新阶段是指组件重新渲染的过程,组件 state 的更新(调用 setState())和父组件渲染都会触发

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

2.1.4.1 static getDerivedStateFromProps()

同挂载阶段,更新阶段也会触发该生命周期函数

2.1.4.2 shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

发生在更新阶段,getDerivedStateFromProps 之后,render 之前,该函数会返回一个布尔值,决定了后续是否执行 render,首次渲染不会调用该函数

import React from 'react';
import Child from './Child';
 
export default class ShouldComponentUpdateComponent extends React.Component {
      constructor(...args) {
          super(...args);
        this.state = {
            n: 1,
        }
    }
 
      render() {
        return(
            <div>
                    <h2 onClick={e=> {
                    this.setState({n: this.state.n + 1})
                }}>n: {this.state.n}</h2>
                <Child value={this.state.n} />
              </div>
        )
    }
}
import React from 'react';
 
export default class Child extends React.Component {
 
    constructor(...props) {
        super(...props);
 
        this.state = {
            value: this.props.value
        };
    }
 
      shouldComponentUpdate(nextProps, nextState) {
        return this.state.value !== nextState.value;
    }
 
      render() {
        console.log('render');
        return(
            <div>
                value: {this.state.value}
                <button onClick={e=>{
                    this.setState({
                        value: this.state.value + 1
                    })
                }}>+</button>
            </div>
        );
    }
}

此方法仅作为性能优化的方式而存在,不要企图依靠此方法来“阻止”渲染,因为可能会产生一些问题。其次,在 React.js 中本来对渲染已经做了必要的优化了,所以通过该函数本质上不能带来特别大的明显提升,且容易增加组件的复杂性,变得难以维护,除非确定使用它能为当前组件带来显著的性能提升

官方后期也会更改该方法的特性,即使返回 false 仍可能会重新渲染组件

不推荐滥用该函数

2.1.4.2.1 example06
2.1.4.2.1.1 example06-1

app.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
import Mail from "./components/Mail";
 
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                    })
                }}>父组件的按钮</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />
 
                <hr />
 
            </div>
        )
    }
 
}
 
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('组件初始化');
    }
 

    // static getDerivedStateFromProps(props, state) {
    //     // 从props中获取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    
    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

无论是父组件还是子组件,都会更新,即调用render。那如何控制呢?

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-1
Branch:branch3

commit description:v2.06-1-example06-1(无论是父组件还是子组件,都会更新,即调用render。那如何控制呢?)

tag:v2.06-1

2.1.4.2.1.2 example06-2

可利用shouldComponentUpdate生命周期,控制子组件不刷新,即更改父组件数据子组件也不更新了。

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('组件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 从props中获取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

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

    shouldComponentUpdate(nextProps, nextState, nextContext) {
        return this.state.value !== nextState.value;
    }

    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

无论父组件更新了,还是子组件所依赖的数据发生变化,子组件也不去更新了!

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-2
Branch:branch3

commit description:v2.06-2-example06-2(可利用shouldComponentUpdate生命周期,控制子组件不刷新,即更改父组件数据子组件也不更新了。)

tag:v2.06-2

2.1.4.2.1.3 example06-3

我们打印参数看看:

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('组件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 从props中获取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

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

    shouldComponentUpdate(nextProps, nextState, nextContext) {
        // return this.state.value !== nextState.value;

        console.log(this.props, nextProps);
        console.log(this.state, nextState);
        return true;
    }

    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                        // pVal: this.state.pVal + 1
                    })
                }}>父组件的按钮</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />

                <hr />

            </div>
        )
    }
}

export default App;

发现打印的两个值都是一样的,因为在父组件操作的是pVal2,我们改成pVal看看。然后我们就可以根据这两个值的差异性来决定是否更新还是不更新了。

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        // pVal2: this.state.pVal2 + 1
                        pVal: this.state.pVal + 1
                    })
                }}>父组件的按钮</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />

                <hr />

            </div>
        )
    }
}

export default App;

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-3
Branch:branch3

commit description:v2.06-3-example06-3(打印shouldComponentUpdate生命周期参数)

tag:v2.06-3

2.1.4.2.1.4 example06-4

父组件操作的是pVal2,父组件更新了pVal2,父组件实际就没影响子组件,子组件就不去更新!除非操作的属性与子组件相关,子组件才会执行render方法。因此可以通过这种方式,来控制子组件到底需不需要更新。这就起到了一定的优化作用!

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 组件当中生命周期的第一个函数
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('组件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 从props中获取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

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

    // nextProps nextState 均代表最新的
    shouldComponentUpdate(nextProps, nextState) {
        // return this.state.value !== nextState.value;

        console.log(this.props, nextProps);
        console.log(this.state, nextState);
        return (this.props.val !== nextProps.val || this.state.a !== nextState.a);
    }

    render() {
        console.log('组件开始渲染了');
        return (
            <div>
                <h2>生命周期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子组件的按钮</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

演示操作的是app的pVal2。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-4
Branch:branch3

commit description:v2.06-4-example06-4(shouldComponentUpdate,通过这种方式,来控制子组件到底需不需要更新。这就起到了一定的优化作用!)

tag:v2.06-4

2.1.4.3 render()

同上

2.1.4.4 getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

该方法在 render() (render方法只是将jsx解析成虚拟dom,然后进行了比较)之后(在将虚拟dom解析成真实dom结构之前调用该函数,即该生命周期),但是在输出到 DOM 之前执行,用来获取渲染之前的快照。当我们想在当前一次更新前获取上次的 DOM 状态,可以在这里进行处理,该函数的返回值将作为参数传递给下个生命周期函数 componentDidUpdate。该函数的作用是,获取真实dom更新之前上一次dom的状态。

该函数并不常用。(官网有例子(滚动条),应用:比如有些时候要更新某个元素,需要不停添加数据,就如同聊天对话窗口一样,这个时候滚动条可能会出现一些问题,因为滚动条它本身不会根据内容来更新滚动条的位置,这个时候需要根据添加的内容和原始的状态,去实时更新滚动条的位置。因为滚动条并不是通过state来控制的(react控制的),这个时候需要根据原生dom来完成!可在这里进行处理。)

(又如:如果有两个元素,b元素依赖于a元素的状态,依赖它的宽和高,即它在页面当中上一次的状态来进行变化,这个时候就可以在这个生命周期里进行处理了)

2.1.4.5 componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

该函数会在 DOM 更新后立即调用,首次渲染不会调用该方法。我们可以在这个函数中对渲染后的 DOM 进行操作

snapshot上一次产生的快照(getSnapshotBeforeUpdate(prevProps, prevState)的返回值),如元素渲染之后,想改变b元素的宽高,这个时候又依赖于a元素,在上一个生命周期当中返回a元素在更新之前的原始值,然后传递给componentDidUpdate,最终再更新b元素。

2.1.5 卸载阶段

当组件从 DOM 中移除时会调用如下方法

  • componentWillUnmount()

2.1.5.1 componentWillUnmount()

componentWillUnmount()

该方法会在组件卸载及销毁前调用,我们可以在这里做一些清理工作,如:组件内的定时器、未完成的请求等

2.1.6 错误处理(比较实用)

当渲染过程,子组件的构造函数或生命周期中抛出错误时,会调用如下方法

  • static getDerivedStateFromError()
  • componentDidCatch()

2.1.6.1 static getDerivedStateFromError()

static getDerivedStateFromError(error)

该方法用来获取子组件抛出的错误,返回值是一个对象,该对象被存储在 state 中,在后续的 render 方法中就可以根据这个对象的值来进行处理,如:显示不同的 UI

class ErrorBoundary extends React.Component {
      constructor(props) {
      super(props);
      this.state = { hasError: false };
  }
 
  static getDerivedStateFromError(error) {
      return { hasError: true };
  }
 
  render() {
      if (this.state.hasError) {
            return <div>出错了</div>;
      }
      return this.props.children;
  }
}
2.1.6.1.1 example07

错误处理。

2.1.6.1.1.1 example07-1

react-Novice04\app\src\components\ErrorDemo.js

import React from 'react';

export default class ErrorDemo extends React.Component {

    // 组件有的时候在执行过程中,可能会不可预料地产生一些错误。给用户展示不太好的体验!
    constructor(props) {
        super(props);

        console.lo('...');
    }

    render() {
        return (
            <div>
                这是正常的页面
            </div>
        );
    }

}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";
import ErrorDemo from "./components/ErrorDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                {/*<button onClick={() => {*/}
                {/*    this.setState({*/}
                {/*        pVal2: this.state.pVal2 + 1*/}
                {/*        // pVal: this.state.pVal + 1*/}
                {/*    })*/}
                {/*}}>父组件的按钮</button>*/}
                {/*<hr/>*/}
                {/*<LifeCycleDemo val={this.state.pVal} />*/}

                {/*<hr />*/}
                <ErrorDemo />
            </div>
        )
    }
}

export default App;

这个错误显示出来以后一定不太好,调试阶段还好说,要是上线了肯定不希望这样显示。上线之后就是空白一片,上线以后非开发环境,肯定不会详细报错了,真正错误就隐藏了。实际上最好的处理,有的时候出错,可能就是一个很小的组件出错。

image-20200605155449314

当渲染过程,子组件的构造函数或生命周期中抛出错误时,会调用如下方法

  • static getDerivedStateFromError() (错误边界处理)
  • componentDidCatch()

边界处理一般放在父组件进行处理,但通常情况下,会封装一个特殊的组件,它不显示任何东西。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.07-1
Branch:branch3

commit description:v2.07-1-example07-1(页面错误不友好提示)

tag:v2.07-1

2.1.6.1.1.2 example07-2

react-Novice04\app\src\components\ErrorBoundary.js

import React from 'react';

// ErrorBoundary 错误边界
export default class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false }; // 默认没错
    }

    // 该方法用来获取子组件抛出的错误,自动被调用
    static getDerivedStateFromError(error) {
        return { hasError: true };  // 返回的对象会更新state,类似调用this.setState({ hasError: true })
    }

    render() {
        if (this.state.hasError) {
            return <div>出错了</div>;
        }
        return this.props.children;
    }
}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";
import ErrorBoundary from "./components/ErrorBoundary";
import ErrorDemo from "./components/ErrorDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                {/*<button onClick={() => {*/}
                {/*    this.setState({*/}
                {/*        pVal2: this.state.pVal2 + 1*/}
                {/*        // pVal: this.state.pVal + 1*/}
                {/*    })*/}
                {/*}}>父组件的按钮</button>*/}
                {/*<hr/>*/}
                {/*<LifeCycleDemo val={this.state.pVal} />*/}

                {/*<hr />*/}
                {/*<ErrorDemo />*/}
                <ErrorBoundary>
                    <ErrorDemo />
                </ErrorBoundary>
            </div>
        )
    }
}

export default App;

打包后的页面,不会有问题,提示出错了这个页面,来进行友好提示。

注意这只会捕获子组件错误,可以把它包在根组件或者重要组件的外部!

            <ErrorBoundary>
                <重要的组件 />
            </ErrorBoundary>

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.07-2
Branch:branch3

commit description:v2.07-1-example07-2(页面错误友好提示)

tag:v2.07-2

2.1.6.2 componentDidCatch()

componentDidCatch(error, info)

该方法与 getDerivedStateFromError() 类似,但是也有不同的地方:

  • 该方法会有一个记录详细错误堆栈信息的 info 参数
  • 该方法可以执行一些额外的操作:打印错误、上报错误信息……


(后续待补充)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章