React - Vòng đời của một Component có gì hay

Sơ bộ về component lifecycle

Trong cuộc sống thường thường, mọi thứ đều sẽ hoạt động theo một chu kì nào đó ví dụ như con người lúc thức dậy thì cần mở mắt sau đó mới làm đến những việc khác. Thì tương tự trong React Component cũng sẽ hoạt động theo một chu kì nào đó mà nhờ đó chúng ta có thể theo dõi và thao tác, xử lý dữ liệu trên các chu kì đó.

Mà cụ thể ở đây các chu kì sẽ là :

  • initialization
  • mounting
  • updating
  • unmounting

Component lifecycle

Trước tiên để có cái nhìn tổng quát nhất về component lifecycle thì những người anh em hãy nhìn ảnh này để thấy được thứ tự các phương thức sẽ được gọi trong một chu kì.

Các giai đoạn ở các phiên bản trước

Các giai đoạn ở phiên bản bây giờ (v17)

Sự khác biệt giữa ngày ấy và bây giờ

Nhìn vào hai ảnh trên thì các bạn cũng thấy rõ sự thay đổi.

Đã có 3 method lifecycle bị loại bỏ

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

Những method này trong phiên bản hiện tại được đánh dấu là unsafe rồi, tên gọi của chúng được thay thế bằng

    UNSAFE_methodName
    // Ví dụ 
    UNSAFE_componentWillMount

Thêm vào đó là sự xuất hiện của 2 method mới là

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate

Note: Ở bài này mình sẽ giới thiệu cả những method cũ luôn để các bạn thấy được sự khác biệt.

Why

Muốn biết tại sao người ta lại có những thay đổi những method này thì các bạn hãy đọc bài viết này, uy tín luôn https://viblo.asia/p/react-lifecycle-methods-are-changing-in-v170-bJzKmMokK9N.

Chú ý: Component Lifecycle này chỉ áp dụng đối với các class component. Nếu sử dụng functional component chúng ta sẽ không thể truy cập tới từ khóa this cũng như các method lifecycle.

Theo như mình được biết thì ở phiên bản hiện tại thì nếu muốn sử dụng các method lifecycle này thì sẽ dùng đến method useEffect() trong React hook. Cái này khi nào mình tìm hiểu mình sẽ viết bài chia sẻ sau. 🤣🤣🤣

Common React Lifecycle Methods

render()

Trước tiên phải nói đến một method được sử dụng nhiều nhất trong class component đơn giản vì đây là method bắt buộc ở trong class component, được dùng để render component.

Như ảnh trên thì render() sẽ được gọi lại ở các chu kì là moutingupdating.

Lưu ý: Không được gọi setState() trong hàm này, bởi khi gọi setState() thì hàm render sẽ được gọi => gây ra lặp vô hạn.

class App extends Component{
   render(){
      return <div>Hello world</div>
   }
}

Không linh tinh luyên thuyên nữa, đi vào tìm hiểu từng quá trình xảy ra trong một component thôi.

1. Initialization

constructor

Phương thức constructor() là phương thức được gọi đầu tiên trong class component của bạn. Nhớ là cái này không áp dụng đối với function component.

Thường thường React sẽ khởi tạo các state, props ở trong constructor().

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Quang Phú'
    };
  }
}

2. Mouting

Đây là quá trình sẽ được gọi sau khi initialization hoàn thành. Nó sẽ thực hiện việc chuyển virtual DOM trong React thành DOM và hiển thị trên trình duyệt. Component sẽ mount trong lần render đầu tiên.

Sẽ có 3 methods sẽ lần lượt được gọi ở quá trình này là:

  • UNSAFE_componentWillMount
  • getDerivedStateFromProps
  • render
  • componentDidMount

UNSAFE_componentWillMount

Chú ý: Đây là method ở các phiên bản cũ

Phương thức này sẽ được gọi tới trước khi một component chuẩn bị được mount, trước khi phương thức render() được gọi. Sau phương thức này thì component sẽ được mounted.

Ở trong phương thức này thì chúng ta có thể gọi tới API cũng như truy cập đến state hay props rồi, nhưng có một lưu ý là khi gọi API response ở đây rồi dùng setState để cập nhật dữ liệu, vì thời gian chuẩn bị mount -> mount rất ngắn nên đôi khi kết quả lúc render component có thể không như mong muốn.

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Quang Phu',
    }
  };

  UNSAFE_componentWillMount() {
    console.log('component will mount');
    console.log(this.state.name);
  };
  
  ...
}

Bạn thử reload lại trình duyệt xem, sẽ thấy hàm componentWillMount được gọi.

getDerivedStateFromProps

static getDerivedStateFromProps(nextProps, prevState) {
    // code
}

Phương thức getDerivedStateFromProps() được gọi ngay trước khi render component. Đây là static method nên do đó chúng ta không thể thao tác với this trong method này. Phương thức này được dùng để thiết lập lại state dựa trên props ban đầu.

static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.data == prevState.data) {
      return null;
    }

    return { data: nextProps.data };
  }

Method này sẽ nhận 2 tham số truyền vào là giá trị mới của props và giá trị cũ của state trước khi được update. Nếu không có cập nhật state mới chúng ta chỉ cần return null. Nếu có cập nhật thì trả về 1 object chứa giá trị thay đổi cho state là được.

componentDidMount

Sau khi 2 phương thức componentWillMountrender thì componentDidMount là method được gọi cuối cuối trong quá trình này.

Nghĩa là sau khi render component xong. Nếu ứng dụng của bạn cần gọi đến các web API khác, sử dụng AJAX để fetch dữ liệu, hay cập nhật dữ liệu thì đây là nơi hợp lý nhất để gọi tới các API khác.

Đến tới hàm này thì các phần tử đã được sinh ra, và chúng ta có thể tương tác với DOM bằng JS trong hàm này.

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Quang Phu',
    }
  };

  UNSAFE_componentWillMount() {
    console.log('component will mount');
  };

  componentDidMount() {
    console.log('component did mount');
  }

  render() {
    console.log('render call');
    return (
      <div className="container">
        <p>{this.state.name}</p>
      </div>
    )
  }
}

Thử chạy chương trình lên chúng ta sẽ thấy lần lượt các phương thức được gọi.

3. Updating

Đây là quá trình thứ 3 được gọi quá trình initializationrender đầu tiên(mount). Quá trình này sẽ được gọi khi chúng ta render component lần thứ 2 trở lên.

Trong giai đoạn này propsstate sẽ được cập nhật khi bạn tác một một sự kiện để cập nhật trạng thái của propsstate, điều này dẫn đến việc re-render lại component.

Khi một instance trong component được cập nhật nó sẽ lần lượt gọi đến các methods.

  • componentWillReceiveProps (đối với props)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • UNSAFE_componentWillUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

UNSAFE_componentWillReceiveProps

Chú ý: Đây là method ở các phiên bản cũ Phương thức này sẽ được gọi khi props được truyền đi được cập nhật. Mình có 1 ví dụ này khá dễ hiểu.

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: 0,
    }
  };

  UNSAFE_componentWillMount() {
    console.log('component will mount');
  };

  componentDidMount() {
    console.log('component did mount');
  };

  increament() {
    this.setState({data: this.state.data +1});
  }

  render() {
    console.log('render call')
    return (
      <div className="container">
        <button onClick={() =>this.increament()}>Increament</button>
        <Result myNumber={this.state.data} />
      </div>
    )
  }
}

class Result extends React.Component {
  UNSAFE_componentWillReceiveProps(newProps) {
    console.log('new props called');
    console.log(newProps);
  };

  render() {
    return (
      <div>
        <h3>{this.props.myNumber}</h3>
      </div>
    )
  }
}

Kết quả

Lưu ý : componentWillReceiveProps nhận tham số truyền vào là giá trị mới của props sau khi được thay đổi.

getDerivedStateFromProps

Cái này dùng cho quả quá trình mouting lẫn updating, mình đã nói ở trên rồi.

shouldComponentUpdate

Phương thức này sẽ chạy trước khi hàm render() được gọi. Phương thức này sẽ kiểm tra xem component có được render lại hay không.

Giá trị trả về của hàm này là true hoặc false. Mặc định sẽ là true, nếu trả về false component của bạn sẽ không được render lại.

Hàm này sẽ nhận hai tham số truyền vào là nextPropsnextState tương ứng với giá trị mới của propsstate.

UNSAFE_componentWillUpdate

Chú ý: Đây là method ở các phiên bản cũ

Hàm này được gọi ngay sau phương thức shouldComponentUpdate(nếu trả về true), ngay trước khi giá trị mới của propsstate được nhận. Hàm này cũng sẽ nhận 2 tham số truyền vào là nextPropsnextState.

Chú ý : Nếu muốn lấy giá trị propsstate cũ ở trong hai phương thức componentWillUpdateshouldComponentUpdate thì chỉ cần sử dụng this.props hoặc this.state để lấy giá trị cũ.

Chúng ta không thể gọi this.setState() trong hàm này được, vì việc gọi this.setState() cũng trigger tới componentWillUpdate khiến xảy ra một vòng lặp vô hạn.

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState) {
    // code
}

Hàm này được sử dụng để thay thế choa componentWillUpdate, được gọi ngay trước khi DOM update.

Có một đặc biệt là hàm này sẽ trả về một giá trị, mà gía trị này sẽ được sử dụng trong componentDidUpdate nên trong componentDidUpdate sẽ có thêm 1 tham số thứ 3 tương ứng với giá trị mà hàm này trả về.

getSnapshotBeforeUpdate(prevProps, prevState) {
      console.log(prevProps);
      console.log(prevState);
      return 999;
  }

componentDidUpdate(prevProps, prevState, snapshot) {
    console.log(snapshot); // 999
 }

componentDidUpdate

Phương thức này được gọi khi component đã re-render xong. Khác với componentWillUpdateshouldComponentUpdate thì componentDidUpdate nhận tham số truyền vào là prevPropsprevState tương ứng với gía trị cũ của propsstate.

Trong phương thức này chúng ta cũng có thể thao tác với DOM trong JS rùi.

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: 0,
    }
  };

  UNSAFE_componentWillMount() {
    console.log('component will mount');
  };

  componentDidMount() {
    console.log('component did mount');
  };

  increament() {
    this.setState({data: this.state.data +1});
  }

  render() {
    console.log('render call')
    return (
      <div className="container">
        <button onClick={() =>this.increament()}>Increament</button>
        <Result myNumber={this.state.data} />
      </div>
    )
  }
}

class Result extends React.Component {
  UNSAFE_componentWillReceiveProps(newProps) {
    console.log('new props called');
    console.log(newProps);
  };
  
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps called');
    if (nextProps.data == prevState.data) {
      return null;
    }

    return { data: nextProps.data };
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate called');
    return true;
  };

  UNSAFE_componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate called');
  };
  
  getSnapshotBeforeUpdate(prevProps, prevState) {
      console.log("getSnapshotBeforeUpdate called");
      return 999;
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('componentDidUpdate called');
  }

  render() {
    return (
      <div>
        <h3>{this.props.myNumber}</h3>
      </div>
    )
  }
}

export default App

4. Unmounting

Đây là giai đoạn cuối cùng trong component, quá trình này xảy ra khi component bị loại bỏ ra khỏi DOM.

Trong giai đoạn này chỉ có mỗi một phương thức là componentWillUnmount(). Trong hàm này người ta thường sẽ dùng để hủy các timer, cancel các request...

class App extends React.Component {
  ...
  componentWillUnmount() {
    console.log('component will unmount')
  }
  ...
}

Lưu ý: Không thể gọi setState trong này vì component sẽ không thể render lại.

setState() trong lifecycle method

Qua những giới thiệu trên mình sẽ tổng kết lại một chút để xem chúng ta nên sử dụng setState và không được sử dụng trong các phương thức nào trong các giai đoạn diễn ra tại một component.

Phương thức Yes/No
constructor() Không, vì đây là nơi chúng ta khởi tạo giá trị của state
UNSAFE_componentWillMount() Không, vì đây là quá trình diễn ra trước khi render component, nên việc dùng setState trong này không làm thay đổi giá trị
getDerivedStateFromProps Không
componentDidMount() Có, nhưng sẽ gây ra việc re-render ngay sau render được thực thi
render() Không, sẽ dẫn tới vòng lặp vô hạn đơn vì setState gọi tới render()
UNSAFE_componentWillReceiveProps
shouldComponentUpdate Không
UNSAFE_componentWillUpdate Không, vì sẽ dẫn tới infinite loop
getSnapshotBeforeUpdate Không
componentDidUpdate Có, nhưng phải bọc trong câu lệnh điều kiện nếu không sẽ xảy ra infinite loop
componentWillUnmount Không

Kết luận

Trên đây là những tìm hiểu được của mình về vòng đời trong một component trong một ứng dụng React.

Bài viết nếu có sai xót các bạn hãy comment ở dưới, nếu không hãy tặng mình 1 upvote cho hứng khởi. 🤣🤣🤣


This work by Nguyen Quang Phu is licensed under CC BY-NC-ND 4.0