MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 组件生命周期详解

2021-03-177.2k 阅读

React 组件生命周期概述

在 React 应用程序中,组件就像一个个独立的小单元,它们有着自己的生命周期。理解组件生命周期,对于编写高效、可维护的 React 代码至关重要。

React 组件的生命周期可以分为三个主要阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。每个阶段都有特定的生命周期方法,开发者可以在这些方法中编写自定义逻辑。

挂载阶段

挂载阶段是组件被插入到 DOM 中的过程,在这个阶段会依次调用以下几个生命周期方法:

constructor()

constructor() 是 ES6 类的构造函数,在 React 组件中,它是组件实例化时第一个被调用的方法。在 constructor() 中,通常会做两件事:初始化 state 和绑定事件处理函数。

以下是一个简单的示例:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState({
            count: this.state.count + 1
        });
    }

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.handleClick}>Increment</button>
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,constructor(props) 接受 props 参数,并通过 super(props)props 传递给父类的构造函数。然后初始化了 state 中的 count 为 0,并将 handleClick 方法绑定到组件实例上,这样在 render 方法中的 button 点击事件中才能正确调用 handleClick

getDerivedStateFromProps(nextProps, prevState)

getDerivedStateFromProps 是一个静态方法,它在组件挂载以及更新时都会被调用。这个方法的主要目的是根据 props 的变化来更新 state

它接收两个参数:nextProps 表示即将更新的 propsprevState 表示之前的 state。返回值会被用于更新 state,如果返回 null,则不更新 state

以下是一个示例,展示如何根据 props 的变化更新 state

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: this.props.initialValue
        };
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.initialValue!== prevState.value) {
            return {
                value: nextProps.initialValue
            };
        }
        return null;
    }

    render() {
        return (
            <div>
                <p>Value: {this.state.value}</p>
            </div>
        );
    }
}

export default MyComponent;

在这个例子中,当 props 中的 initialValue 发生变化时,getDerivedStateFromProps 方法会更新 state 中的 value

render()

render() 方法是 React 组件中唯一必须实现的方法。它负责返回描述组件 UI 的 JSX 元素。这个方法应该是纯函数,即不应该修改 state 或产生任何副作用。

例如:

import React, { Component } from 'react';

class MyComponent extends Component {
    render() {
        return (
            <div>
                <h1>Hello, React!</h1>
            </div>
        );
    }
}

export default MyComponent;

render 方法返回的 JSX 会被 React 解析并渲染到 DOM 中。

componentDidMount()

componentDidMount() 方法在组件被插入到 DOM 后立即调用。在这个方法中,可以执行一些需要 DOM 元素存在才能进行的操作,比如初始化第三方库、发起网络请求等。

以下是一个发起网络请求获取数据并更新 state 的示例:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            data: null
        };
    }

    componentDidMount() {
        fetch('https://example.com/api/data')
          .then(response => response.json())
          .then(data => {
                this.setState({
                    data: data
                });
            });
    }

    render() {
        return (
            <div>
                {this.state.data? (
                    <p>{JSON.stringify(this.state.data)}</p>
                ) : (
                    <p>Loading...</p>
                )}
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,componentDidMount 方法中通过 fetch 发起网络请求,获取数据后更新 staterender 方法根据 state 中的数据显示相应的内容。

更新阶段

当组件的 propsstate 发生变化时,组件会进入更新阶段,这个阶段会调用一系列的生命周期方法。

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate 方法在组件接收到新的 propsstate 时被调用,它的返回值决定组件是否需要更新。如果返回 true,则组件会继续更新流程;如果返回 false,则组件不会更新,render 方法也不会被调用。

这个方法可以用于性能优化,避免不必要的重新渲染。例如:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextState.count!== this.state.count) {
            return true;
        }
        return false;
    }

    handleClick() {
        this.setState({
            count: this.state.count + 1
        });
    }

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.handleClick}>Increment</button>
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,只有当 state 中的 count 发生变化时,shouldComponentUpdate 才返回 true,组件才会更新。

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate 方法在 render 方法之后,componentDidUpdate 方法之前被调用。它的主要作用是在 DOM 更新之前获取一些信息,比如滚动位置等,这些信息可以在 componentDidUpdate 方法中使用。

它接收 prevPropsprevState 作为参数,返回值会作为 componentDidUpdate 的第三个参数。

以下是一个记录滚动位置的示例:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            list: Array.from({ length: 100 }, (_, i) => i + 1)
        };
        this.listRef = React.createRef();
    }

    getSnapshotBeforeUpdate(prevProps, prevState) {
        const list = this.listRef.current;
        return list.scrollTop;
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        const list = this.listRef.current;
        list.scrollTop = snapshot;
    }

    render() {
        return (
            <div>
                <ul ref={this.listRef}>
                    {this.state.list.map(item => (
                        <li key={item}>{item}</li>
                    ))}
                </ul>
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,getSnapshotBeforeUpdate 获取滚动条的位置,并在 componentDidUpdate 中恢复滚动条位置,这样在组件更新时滚动位置不会丢失。

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate 方法在组件更新后被调用。可以在这个方法中执行一些依赖于 DOM 更新完成后的操作,比如更新第三方 UI 库等。

它接收 prevPropsprevStatesnapshot 作为参数,snapshotgetSnapshotBeforeUpdate 的返回值。

例如:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            text: 'Initial Text'
        };
    }

    handleChange = (e) => {
        this.setState({
            text: e.target.value
        });
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevState.text!== this.state.text) {
            console.log('Text has been updated:', this.state.text);
        }
    }

    render() {
        return (
            <div>
                <input type="text" value={this.state.text} onChange={this.handleChange} />
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,当 state 中的 text 发生变化时,componentDidUpdate 会在控制台打印出更新后的文本。

卸载阶段

当组件从 DOM 中移除时,会进入卸载阶段,此时会调用 componentWillUnmount 方法。

componentWillUnmount()

componentWillUnmount 方法在组件即将从 DOM 中移除时被调用。可以在这个方法中执行一些清理操作,比如取消网络请求、清除定时器等。

以下是一个清除定时器的示例:

import React, { Component } from 'react';

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            time: new Date().toLocaleTimeString()
        };
        this.timer = null;
    }

    componentDidMount() {
        this.timer = setInterval(() => {
            this.setState({
                time: new Date().toLocaleTimeString()
            });
        }, 1000);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }

    render() {
        return (
            <div>
                <p>Current Time: {this.state.time}</p>
            </div>
        );
    }
}

export default MyComponent;

在上述代码中,componentDidMount 方法中设置了一个定时器来更新时间,componentWillUnmount 方法中清除了定时器,避免内存泄漏。

错误处理

在 React 16 及以上版本中,引入了错误边界的概念,用于捕获子组件树中的 JavaScript 错误,并渲染备用 UI,而不是崩溃整个应用。

componentDidCatch(error, errorInfo)

componentDidCatch 方法用于捕获子组件树中的错误。它接收两个参数:error 表示错误对象,errorInfo 包含错误发生的组件栈信息。

以下是一个简单的错误边界示例:

import React, { Component } from 'react';

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

    componentDidCatch(error, errorInfo) {
        console.log('Error caught:', error, errorInfo);
        this.setState({
            hasError: true
        });
    }

    render() {
        if (this.state.hasError) {
            return (
                <div>
                    <p>An error has occurred.</p>
                </div>
            );
        }
        return this.props.children;
    }
}

class MyComponent extends Component {
    render() {
        throw new Error('Simulated error');
        return (
            <div>
                <p>This will not be rendered.</p>
            </div>
        );
    }
}

class App extends Component {
    render() {
        return (
            <ErrorBoundary>
                <MyComponent />
            </ErrorBoundary>
        );
    }
}

export default App;

在上述代码中,ErrorBoundary 组件捕获了 MyComponent 中抛出的错误,并显示错误提示信息,而不会导致整个应用崩溃。

总结 React 组件生命周期的应用场景

  1. 数据获取:在 componentDidMount 中发起网络请求获取数据,用于初始化组件的 state。例如,从 API 获取用户信息并显示在页面上。
  2. 性能优化:通过 shouldComponentUpdate 方法根据 propsstate 的变化判断是否需要重新渲染组件,避免不必要的更新,提高性能。比如列表组件中,只有当列表数据变化时才重新渲染。
  3. DOM 操作:在 componentDidMountcomponentDidUpdate 中进行 DOM 操作,比如初始化一些依赖于 DOM 元素的第三方库,如图表库、日期选择器等。
  4. 清理操作:在 componentWillUnmount 中进行清理工作,如取消网络请求、清除定时器等,防止内存泄漏。例如,当一个组件中有定时器不断更新数据,在组件卸载时需要清除定时器。
  5. 错误处理:使用错误边界的 componentDidCatch 方法捕获子组件中的错误,并提供友好的错误提示,保证应用的稳定性。比如在一个复杂的表单组件中,捕获输入验证等相关错误并提示用户。

通过深入理解 React 组件生命周期的各个阶段和方法,开发者能够更好地控制组件的行为,编写高效、健壮的 React 应用程序。在实际开发中,根据不同的需求和场景,合理运用这些生命周期方法,将有助于提升应用的性能和用户体验。