ReactJS Main Concept: State and Lifecycle
Bài đăng này đã không được cập nhật trong 4 năm
Introduction

Như đã biết ở phần trước, ReactJS có một quy tắc chung là props của component luôn luôn là read-only, vì thế nên để không vi phạm vào quy tắc này, state ra đời.
Cũng giống như props, state cũng có nhiệm vụ chứa các thông tin của component. Tuy nhiên state chỉ hoạt động trong component của nó (private), là thành phần của component, và là loại dữ liệu động (có thể thay đổi được). Nếu 1 component cần quản lý state, thì component đó nên là class component hơn là function component.
Example
Cùng quay trở lại với vd ticking clock ở phần đầu, là một trong những cách để update UI thông qua việc gọi ReactDOM.render() nhiều lần:
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);
Áp dụng kiến thức về Component và Props ở phần trước, ta có thể khởi tạo component Clock và refactor lại vd trên như sau:
function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);
Tuy nhiên, ta có thể tối ưu hơn nữa bằng cách để component Clock tự khởi tạo biến date và update chính nó theo thời gian.
Tức là giá trị của date sẽ phải được thay đổi liên tục, điều này sẽ vi phạm đến nguyên tắc read-only của props. Vì vậy chúng ta sẽ cần đến state và sức mạnh của các lifecycle methods.
Việc cần làm đầu tiên, đó là biến  Clock từ function component trở thành class component.
Converting a Function to a Class
Để khởi tạo class component, ta sẽ thực hiện lần lượt các bước như sau:
- tạo 1 ES6 class với tên là Clock, extendReact.Component
- sử dụng method render()bên trong
- chuyển toàn bộ body của function component cũ vào trong hàm render()
- sử dụng this.props.thay vìprops.như ở function component
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
Bước tiếp theo chúng ta sẽ sử dụng state để lưu lại giá trị của  date cho component Clock.
Adding Local State to a Class
Ta sẽ chuyển date từ props sang state như sau:
Đầu tiên, chuyển this.props.date thành this.state.date ở method render():
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
Sau đó sử dụng constructor để khởi tạo giá trị date và gán vào state:
constructor(props) {
  super(props);
  this.state = {date: new Date()};
}
Và bỏ props date khỏi element <Clock />:
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
Cuối cùng ta được:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
Demo: Try it on CodePen
Bước tiếp theo chúng ta sẽ sử dụng lifecycle method để update giá trị của date theo thời gian và update lại UI tương ứng.
Adding Lifecycle Methods to a Class
Khi một app sử dụng nhiều component, việc giải phóng tài nguyên bị chiếm dụng bởi các component sau khi các component này bị loại bỏ là điều rất quan trọng.
Cụ thể trong bài toán này, để khởi tạo giá trị cho date và update nó liên tục theo thời gian, ta sẽ sử dụng method đếm setInterval.  Tức là setInterval sẽ hoạt động ngay sau khi Clock được render vào DOM lần đầu tiên, trong React điều này được gọi là Mounting. Chúng ta cũng muốn xóa bỏ việc đếm này sau khi Clock bị loại bỏ, quá trình này được gọi là unmounting trong React.
Để làm được những điều đó, chúng ta sẽ thêm các logic tương ứng khi component được mount và unmount thông qua 2 method là componentDidMount() và componentWillUnmount(). Đây chính là Lifecycle Method trong React:
- method componentDidMount()được chạy sau khi component output được render ra ngoài DOM
- method componentWillUnmount()chạy sau khi component bị loại bỏ khỏi DOM
Trở lại với bài toán, chúng ta muốn chạy setInterval ngay sau khi Clock được render, thì ta cần thêm nó vào trong componentDidMount():
componentDidMount() {
  this.timerID = setInterval(
    () => this.tick(),
    1000
  );
}
Chúng ta đã lưu lại giá trị của timer ID vào this.timerID. Khác với this.props, chúng ta có thể thoải mái khai báo thêm các field ở trong class component Clock để lưu lại các giá trị mà không ảnh hưởng đến luồng chính của logic, vd như this.timerID bên trên.
Tiếp tục, để xóa bỏ quá trình đếm sau khi Clock bị loại bỏ khỏi DOM, ta sẽ sử dụng method clearInterval và cho nó vào trong componentWillUnmount():
 componentWillUnmount() {
    clearInterval(this.timerID);
  }
Sau đó là tạo function tick(), trong đó sử dụng method this.setState() để set lại giá trị của date mỗi khi tick() được gọi:
  tick() {
    this.setState({
      date: new Date()
    });
  }
Cuối cùng ta được:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
  tick() {
    this.setState({
      date: new Date()
    });
  }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
Demo: Try it on CodePen
Tổng kết lại ta có luồng xử lí như sau:
- Khi <Clock />được truyền vàoReactDOM.render(), React sẽ gọi đến constructor của componentClocksau đó khởi tạo giá trị state là object chứa time hiện tại
- Sau đó React gọi đến method render()của componentClockvà update DOM tương ứng với output của methodrender()
- Sau khi output của component Clockđược render vào DOM, React sẽ gọi đến lifecycle methodcomponentDidMount(). Lúc này bộ đếm sẽ được khởi tạo và gọi đến methodtick()theo thời gian đã set
- Cứ mỗi lần method tick()được gọi, React biết rằng DOM sẽ thay đổi vì giá trị củathis.state.datethay đổi sau khi gọi đến methodsetState()bên trong. Vì thế nên sau đó React sẽ gọi lại methodrender()và DOM sẽ được cập nhật
- Trường hợp component Clockbị remove khỏi DOM, React sẽ gọi đến methodcomponentWillUnmount()để xóa bỏ bộ đếm
Using State Correctly
Do Not Modify State Directly
Chúng ta không nên modify lại giá trị cho state một cách trực tiếp, làm thế thì component tương ứng sẽ không được render lại, vd:
// Wrong
 this.state.date = new Date();
React sẽ trigger việc re-render thông qua method setState(), vì thế nên khi muốn modify giá trị của state, ta nên dùng method setState():
// Correct
this.setState({date: new Date()});
Việc khai báo giá trị cho this.state chỉ được thực hiện ở trong constructor, như vd ticking Clock bên trên.
State Updates May Be Asynchronous
React có thể gộp nhiều lệnh gọi đến setState() vào một batch duy nhất để cải thiện hiệu suất, vì thế nên this.props và this.state có thể được update một cách bất đồng bộ.
Chúng ta cần chú ý khi sử dụng giá trị của props và state để tính toán, vd dưới đây có thể sẽ sai trong 1 vài trường hợp:
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
Để tránh điều này, chúng ta có thể sử dụng dạng khác của method setState(), nhận vào một function thay vì object như thông thường. Function này sẽ nhận state ngay trước đó là tham số đầu tiên, và tham số thứ hai sẽ là giá trị props tại thời điểm việc update đã hoàn thành:
// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));
Thay vì dạng arrow function như trên, ta có thể viết dưới dạng regular function:
// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});
State Updates are Merged
Bản chất của method setState() là merged object bên trong vào giá trị state hiện tại, vd ta khai báo các field cho state ban đầu như sau:
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
Sau đó chúng ta có thể update / modified lại từng field một cách độc lập:
  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });
    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }
Vì là shallow merged, nên this.setState({comments}) sẽ chỉ modified this.state.comments và không đả động gì đến this.state.posts
Summary
Bài viết nhằm giới thiệu về khái niệm State và Lifecycle trong ReactJS, cảm ơn bạn đã dành thời gian theo dõi.
All rights reserved
 
  
 