Tìm hiểu về Vòng đời Component trong React

Trong các ứng dụng React, các component trải qua một chuỗi các sự kiện từ khi được tạo ra, cập nhật cho đến khi bị hủy bỏ. Hiểu rõ vòng đời này giúp tối ưu hóa hiệu suất và quản lý trạng thái hiệu quả. Dưới đây là các giai đoạn chính:

1. Khởi tạo Component

1.1. Constructor

Là phương thức khởi tạo mặc định của lớp trong ES6, tự động được gọi khi một đối tượng component được tạo bằng từ khóa new. Nếu không định nghĩa, một constructor rỗng sẽ được thêm vào mặc định. Khi định nghĩa constructor, bắt buộc phải gọi hàm super(). Để truy cập this.props bên trong constructor, bạn cần truyền props vào như một tham số.

Constructor thường được sử dụng để khởi tạo state ban đầu cho component.


class MyComponent extends React.Component {
    constructor(props) {
        super(props); // Gọi super là bắt buộc
        this.state = {
            dataList: this.props.initialData
        };
        console.log('Constructor called with props:', this.props); // Có thể truy cập this.props
    }

    render() {
        // ...
    }
}

1.2. static getDerivedStateFromProps (nextProps, prevState)

Được gọi ngay trước khi render, cả trong lần mount đầu tiên và các lần cập nhật sau đó. Nó cho phép component cập nhật state nội bộ dựa trên sự thay đổi của props. Phương thức này nên trả về một object để cập nhật state, hoặc null để không thay đổi gì.


static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.someValue !== prevState.someValue) {
        return {
            derivedValue: nextProps.someValue * 2
        };
    }
    return null; // Không có thay đổi state nào
}

1.3. render()

Là phương thức bắt buộc phải có trong mọi React component. Nó chịu trách nhiệm mô tả giao diện người dùng sẽ được hiển thị. Phương thức này không được phép thay đổi state hoặc thực hiện các tác vụ không thuần khiết (side effects), vì làm vậy có thể dẫn đến vòng lặp vô hạn và lỗi tràn bộ nhớ (stack overflow). Hàm render() phải trả về một trong các loại sau: React elements, arrays và fragments, portals, null, hoặc undefined.


render() {
    const { userData = {} } = this.props;
    if (!userData.name) {
        return <p>No user data available</p>;
    }
    return (
        <div className="user-profile">
            <h2>{userData.name}</h2>
            <p>Email: {userData.email}</p>
        </div>
    );
}

2. Cập nhật Component

2.1. shouldComponentUpdate(nextProps, nextState)

Được gọi trước khi render khi các props hoặc state mới được nhận. Phương thức này cho phép bạn kiểm soát xem component có cần phải được render lại hay không. Mặc định, nó trả về true, cho phép cập nhật. Nếu trả về false, các phương thức render và các phương thức cập nhật tiếp theo sẽ bị bỏ qua.

Đây là nơi tuyệt vời để tối ưu hóa hiệu suất bằng cách so sánh nextProps, nextState với this.props, this.state hiện tại.


shouldComponentUpdate(nextProps, nextState) {
    // Chỉ cập nhật nếu giá trị 'id' thay đổi
    if (nextProps.item.id !== this.props.item.id) {
        return true;
    }
    return false;
}

2.2. getSnapshotBeforeUpdate(prevProps, prevState)

Được gọi ngay trước khi DOM được cập nhật từ kết quả của một render. Cho phép component của bạn nắm bắt một số thông tin (ví dụ: vị trí cuộn) từ DOM trước khi nó thay đổi. Giá trị trả về từ phương thức này sẽ được truyền dưới dạng tham số thứ ba cho componentDidUpdate().


getSnapshotBeforeUpdate(prevProps, prevState) {
    // Nếu danh sách đã cuộn xuống
    if (prevState.messages.length < this.props.messages.length) {
        const list = this.messageListRef.current;
        return list.scrollHeight - list.scrollTop; // Lấy vị trí cuộn
    }
    return null;
}

2.3. componentDidUpdate(prevProps, prevState, snapshot)

Được gọi sau khi component đã được cập nhật trong DOM. Phương thức này không được gọi cho lần render đầu tiên. Bạn có thể thực hiện các tác vụ dựa trên sự thay đổi của props hoặc state, tuy nhiên, hãy cẩn thận khi gọi setState() bên trong phương thức này để tránh vòng lặp vô hạn. Luôn cần có điều kiện để gọi setState().


componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
        const list = this.messageListRef.current;
        list.scrollTop = list.scrollHeight - snapshot; // Khôi phục vị trí cuộn
    }
    if (prevProps.userId !== this.props.userId) {
        this.fetchUserData(this.props.userId); // Tải lại dữ liệu nếu userId thay đổi
    }
}

3. Gỡ bỏ Component

3.1. componentWillUnmount()

Được gọi ngay trước khi component bị gỡ bỏ và hủy. Đây là nơi lý tưởng để thực hiện các thao tác dọn dẹp, chẳng hạn như hủy bỏ các bộ đếm thời gian (timers), vô hiệu hóa các kết nối mạng hoặc xóa các event listener đã được đăng ký trong componentDidMount().


componentWillUnmount() {
    console.log('Component will unmount. Cleaning up resources...');
    // Ví dụ: Hủy bỏ interval đã thiết lập
    // clearInterval(this.timerID);
    // Ví dụ: Xóa event listener
    // window.removeEventListener('resize', this.handleResize);
}

4. Các phương thức khác

4.1. componentDidMount()

Được gọi ngay sau khi component được mount (chèn vào DOM) và render lần đầu tiên. Đây là nơi thích hợp để thực hiện các yêu cầu mạng (network requests) để lấy dữ liệu, thiết lập các subscriptions, hoặc thao tác trực tiếp với DOM (ví dụ: sử dụng refs để lấy các phần tử DOM).


componentDidMount() {
    console.log('Component did mount. Fetching initial data...');
    fetch('/api/data')
        .then(response => response.json())
        .then(data => {
            this.setState({ apiData: data });
        });
    // Lấy phần tử DOM thông qua ref
    // this.myInputRef.current.focus();
}

Lưu ý: Một số phương thức như componentWillMount(), componentWillReceiveProps(), và componentWillUpdate() đã bị đánh dấu là không dùng nữa (deprecated) trong các phiên bản React mới hơn (từ v16.3) và đã được thay thế bằng các phương thức an toàn hơn như getDerivedStateFromProps và getSnapshotBeforeUpdate để tránh các lỗi tiềm ẩn.

Demo đơn giản

Dưới đây là ví dụ về cách các phương thức lifecycle được gọi khi một component cha (App) và component con (ChildComponent) tương tác:


// Component Cha
class App extends React.Component {
    constructor(props) {
        super(props);
        console.log('App: Constructor');
        this.state = { message: 'Initial Message' };
    }

    componentDidMount() {
        console.log('App: ComponentDidMount');
        // Giả lập cập nhật state sau một khoảng thời gian
        setTimeout(() => {
            this.setState({ message: 'Updated Message' });
        }, 2000);
    }

    render() {
        console.log('App: Render');
        return (
            <div>
                <h1>Parent Component</h1>
                <ChildComponent appMessage={this.state.message} />
            </div>
        );
    }
}

// Component Con
class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        console.log('ChildComponent: Constructor');
    }

    componentDidMount() {
        console.log('ChildComponent: ComponentDidMount');
    }

    componentDidUpdate(prevProps) {
        console.log('ChildComponent: ComponentDidUpdate. Previous message:', prevProps.appMessage);
    }

    componentWillUnmount() {
        console.log('ChildComponent: ComponentWillUnmount');
    }

    render() {
        console.log('ChildComponent: Render with message:', this.props.appMessage);
        return (
            <p>Message from parent: {this.props.appMessage}</p>
        );
    }
}

// ReactDOM.render(<App />, document.getElementById('root'));

Thẻ: react Lifecycle Methods JavaScript frontend Web Development

Đăng vào ngày 2 tháng 11 lúc 00:07