React(三)——React组件之生命周期

84 篇文章 3 订阅
11 篇文章 1 订阅

目录

1.周期分类

2.挂载阶段

2.1constructor

2.2render()

2.3static getDerivedStateFromProps()

2.4componentDidMount()

3.更新阶段

3.1static getDerivedStateFromProps()

3.2shouldComponentUpdate()

3.3render()

3.4getSnapshotBeforeUpdate()

3.5componentDidUpdate()

4.卸载阶段

4.1componentWillUnmount()

5.错误处理

5.1static getDerivedStateFromError()

5.2componentDidCatch()


生命周期

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

1.周期分类

React.js 为组件的生命周期划分了四个不同的阶段,不同的阶段又会对应着一些不同的函数。

  • 挂载阶段:组件创建到渲染到页面的过程。constructor()、render()、static getDerivedStateFromProps()、componentDidMount()
  • 更新阶段:组件重新渲染的过程,组件 state 的更新(调用 setState())和父组件渲染都会触发。static getDerivedStateFromProps()、shouldComponentUpdate()、render()、getSnapshotBeforeUpdate()、componentDidUpdate()
  • 卸载阶段:当组件从 DOM 中移除时会调用如下方法componentWillUnmount()
  • 错误处理:当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法static getDerivedStateFromError()、componentDidCatch()

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

 

2.挂载阶段

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

  • constructor()
  • render()
  • static getDerivedStateFromProps()
  • componentDidMount()

2.1constructor

constructor(props)

类的构造函数,也是组件初始化函数,只会在初始化创建时执行一次。一般情况下,我们会在这个阶段做一些初始化的工作

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

组件标签的使用<Lifecycle />等同于new Lifecycle()。

2.2render()

render 方法是 Class 组件必须实现的方法。用于渲染和返回组件产生的结构。

2.3static getDerivedStateFromProps(props,state)

static getDerivedStateFromProps(props, state)

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

  1. 该方法是静态方法,所以不能再该方法中使用this(因为静态方法属于类的方法不是属于实例的方法)。
  2. 当组件中的state依赖外部传入的props时(且内部私有状态也会可能会更改),可以使用此生命周期来处理这种需求。如使用this.state={v:this.props.a},外部传入props数据会影响数据a的变化,而内部私有状态state的改变也会影响数据a的变化,此时可以此生命周期方法。
  3. 这个生命周期函数一定要有返回值,返回值将被设置给state

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

需求分析(应用场景):

如下,

  1. 收件人列表中:可以填写收件人邮箱地址。可把收件人列表看做一个独立组件,此收件人列表组件,有一个状态叫收件人列表,此状态可以由自己决定,同时收件人列表组件中有input输入框,当input输入框中输入内容时,就会更新组件私有状态state,这些组件内部的私有行为;
  2. 右侧好友列表组件:好友列表中可以随意选择某个人,选择的地址会传入到收件人列表组件(外部输入数据为props)。
  3. 即可以由外部传入数据到收件人列表组件内部,同时收件人列表组件内部也可以自己控制内部私有数据。

步骤:

收件人列表组件:方法一:使用onKeyDown()/onInput()/onPress()其一绑定该组件的方法keyDown(),该方法用于添加一个新收件人,通过判断(13===keyCode)(建议不要写成keyCode===13,因为一旦写错可能写成keyCode=13成了赋值,判断永远成立)时,将输入地址添加到地址栏(更新私有状态this.setState()),使用[...this.state.users,{name:'',mail:value}]通过扩展运算符将原来的地址和新添加的地址都重新进行更新,最后清空input框target.value = '';

收件人列表组件:方法二:使用非受控组件方式实现同样效果。在this.state中设置email='',再通过onChange监听并更新input框的值;

好友列表组件:点击某个好友时,添加一条好友地址到收件人列表组件。接收外部组件收件人列表组件传入的数据。e=>this.addUser(friends)使用箭头函数方式,首先调用的是箭头函数,当箭头函数执行时,再执行addUser()方法并接收外部传入数据friends。再将点击获取的数据更新到收件人列表组件;

添加的地址时累加到原有地址后而不是覆盖,此时就用到生命周期函数static getDerivedStateFromProps(),这个生命周期函数一定要有返回值,返回值将被设置给state(更新state)。getDerivedStateFromProps()只要是组件重新渲染都会执行,而此时不是每次都需要获得该方法里的props,而是只有得到user数据后才需要即判断if (props.user)时才更新即return {users:[...users,props.users]}。

案例代码实现:

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;
}

 Mail.js:

import React from 'react';

import './mail.css';
import MultiInput from './MultiInput.js';

/**
 * 好友列表组件(父组件):
 * 点击好友列表某个好友时,以prop传入数据给收件人列表组件,并和收件人列表组件中原有邮箱地址进行累加显示
 */
class Mail extends React.Component{
    constructor(props){
        super(props);
        //在constructor()方法中设置初始化数据
        this.state = {
            friends: [
                {id: 1, name: '张三', email: 'zhangsan@email.com'},
                {id: 2, name: '李四', email: 'lisi@email.com'},
                {id: 3, name: '王五', email: 'wangwu@email.com'}
            ],
            user:null
        };

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

    //此处直接将数据设置到user中,而prop数据更改会直接重新渲染自身及子组件,同时渲染时会调用static getDerivedStateFromProps()
    //所以可以在子组件中通过此方法的props参数将更改后的user数据传递过去(一定要通过组件将user传递过去)
    selectFriend(friend){
        //将点击的数据设置到user中
        this.setState({
            user:friend
        });
    }
    render(){
        //获取好友列表数据
        let {friends,user} = this.state;

        return (
            <div className="clear">
                <h1>发送邮件</h1>
                <hr/>
                <ul className="box fr">
                    {/* 好友列表 */}
                    {
                        /*el => this.selectFriend(friends) 由于此处需要传参friends,而直接使用this.selectFriend(friends)会直接执行
                         所以使用箭头函数方式,点击时会调用箭头函数,箭头函数执行时才会执行this.selectFriend(friends)方法并传参
                         */
                        Object.keys(friends).map(item=> <li onClick={el => this.selectFriend(friends[item])} key={friends[item].id}>{friends[item].name}</li>)
                    }
                </ul>
                <div className="fl">
                    {/* 收件人列表组件(子组件):收件人输入框   一定要通过组件将user传递过去,子组件中方法getDerivedStateFromProps需要用到*/}
                    <MultiInput user={user}/>
                </div>
            </div>
        );
    }
}

export default Mail;

 MultiInput.js:

import React from 'react';
import './mail.css';

/**
 * 收件人列表组件(子组件)——DOM操作方式清空input框:
 * 在输入框输入邮箱地址并按enter键时,将地址显示在上方
 */
class MultiInput extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            users: []
        }
        this.keyDown = this.keyDown.bind(this);
    }

    //添加好友方法(收入邮箱地址后添加到输入框上方)
    keyDown({ keyCode, target, target: { value } }) {
        //判断当按下enter键时执行添加操作,keyCode从事件对象Event中解构出来
        if (13 === keyCode) {
            this.setState({
                users: [...this.state.users, {
                    name: '',
                    email: value
                }]
            });
            //操作DOM方式清空输入框
            target.value = '';
        }
    }

    //注意静态方法只能调用静态方法,所以此方法也必须是静态方法
    static addNewUser(user,users){
        //判断email不相等时进行添加
        if ( !users.find(u => u.email === user.email) ) {
            users.push(user);
        }
        // users.push(user);
        return users;
    }

    //将父级props数据和私有state数据更新都会影响的数据用此方法进行处理
    //注意静态方法中没有this,所以不能直接使用this.state改变users
    static getDerivedStateFromProps(props,state){
        if(props.user){
            //将父组件传递的好友和输入的邮箱地址进行累加(注意静态方法中没有this),静态方法调用时必须使用类名进行调用
            MultiInput.addNewUser(props.user,state.users);   
        } 
        //此方法返回值即更新state,返回null不进行更新
        return null;
    }


    render() {
        let { users } = this.state;
        return (
            <div className="multi-input">
                {
                    Object.keys(users).map(item => (
                        <div key={item}>{users[item].name} {users[item].email};</div>
                    ))
                }
                <div>
                    <input type="text" onKeyDown={this.keyDown} />
                </div>
            </div>
        );
    }
}

export default MultiInput;

效果:

 

2.4componentDidMount()

componentDidMount()

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

  • 操作 DOM 节点
  • 发送请求

3.更新阶段

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

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

3.1static getDerivedStateFromProps()

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

3.2shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

发生在更新阶段,getDerivedStateFromProps 之后,render 之前,该函数会返回一个布尔值,决定了后续是否执行 render,首次渲染不会调用该函数。(应该于更新了某个东西,但不需要重新渲染时)。

如下:Child组件初始化时接收父组件传过来的value值,但是不希望后续更新也受父级组件此数据的影响(判断this.state.value!==nextState.value时即如果是私有数据state变化时返回true会重新渲染,如果是props传入数据会返回false就不会重新渲染),自己内部会有自己的value逻辑控制。

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
        };
    }
     //Child组件初始化时接收父组件传过来的value值,但是不希望后续更新也收父级组件此数据的影响,自己内部会有自己的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 仍可能会重新渲染组件,不推荐滥用该函数。

3.3render()

同上

3.4getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

该方法在 render() 之后,但是在输出到 DOM 之前执行,用来获取渲染之前的快照。当我们想在当前一次更新前获取上次的 DOM 状态,可以在这里进行处理,该函数的返回值将作为参数传递给下个生命周期函数 componentDidUpdate。该函数并不常用。

3.5componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

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

4.卸载阶段

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

  • componentWillUnmount()

4.1componentWillUnmount()

componentWillUnmount()

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

5.错误处理

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

  • static getDerivedStateFromError()
  • componentDidCatch()

5.1static 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;
  }
}

案例:父级组件包含子级组件,子级组件出错时,只在对应子级组件处显示错误页面,父级原有组件不受影响。

错误边界组件:ErrorBoundry.js

import React from 'react';

class ErrorBoundry extends React.Component{
    constructor(props){
        super(props);
        //static getDerivedStateFromError()方法返回值为一个对象存放在state中,所以可以在此设置错误标识
        this.state = {
            errorMsg : false
        }
    }
    static getDerivedStateFromError(error){
        //当错误边界组件包含的组件出错时会调用此方法,可将错误标识为true        
        return { errorMsg:true };
    }
    render(){
        //通过判断,如果错误弹出错误页面,否则正常显示
        if (this.state.errorMsg) {
            return <div>某个组件出错了</div>;
      }
      return this.props.children;
    }
}

export default ErrorBoundry;

MyParent.js:

import React from 'react';

import MyChild from './MyChild';
import ErrorBoundry from './ErrorBoundry';

class MyParent extends React.Component{
    render(){
        return (
            <div>
                <h1>这是父级MyParent</h1>
                <ErrorBoundry>
                    <MyChild />
                </ErrorBoundry>
            </div>
        );
    }
}

export default MyParent;

 MyChild.js:

import React from 'react';

class MyChild extends React.Component{
    
    render(){
        let obj = {
            x:12
        }
        return(
            <div>
                <h2>这是子级MyChild</h2>
                {/* 对象不能直接输出所以会出错 */}
                {/* {obj} */}
            </div>
        );
    }
}
export  default MyChild;

效果:

未出错:

 

出错:

5.2componentDidCatch()

componentDidCatch(error, info)

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

  • 该方法会有一个记录详细错误堆栈信息的 info 参数

  • 该方法可以执行一些额外的操作:打印错误、上报错误信息……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值