Thay thế các life cycle method bằng react hooks
Bài đăng này đã không được cập nhật trong 6 năm
1. Introduce
- Trong phiên bản React 16.8.0 được release cách đây không lâu thì react đã giới thiệu khái niệm React Hookshoàn toàn mới, nó mang tới rất nhiều thay đổi đến cách mà chúng ta code một ứng dụng React.
- Hookshướng chúng ta tới- functional componentchứ không phải- class componentnhư trước nữa.
- Ở bài này chúng ta sẽ tìm hiểu xem hook đã làm cho stateless componenttrở thànhstateful componentnhư thế nào.
2. Content
- Khái niệm functional componenthay còn được hiểu làstateless componentcònclass componentthì làstateful component.
- Đúng như tên gọi của nó thì statelessnghĩa là ko có state.
// stateless component
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
    return <h1>Hello world</h1>;
}
ReactDOM.render(<App />, document.getElementById('root'));
- Hoặc stateful nghĩa là chúng ta có thể khai báo và sử dụng state.
// stateful component
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
    state = { message: "Hello world" };
    
    render() {
        return <h1>{this.state.message}</h1>;
    }
}
ReactDOM.render(<App />, document.getElementById('root'));
2.1. State
- Ở trên mình có nói hooks đã làm cho stateless trở thành stateful như nào.
- Khi hook ra đời thì nó cung cấp cho chúng ta api gọi là useStatevà chínhuseStateđã giúpstateless componenttrở thànhstateful component.
- Ta sử dụng nó như sau
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
const App = () => {
    const [message, setMessage] = useState('Hello world');
    return <h1>{message}</h1>;
}
ReactDOM.render(<App />, document.getElementById('root'));
- Và kết quả chúng ta thu được là giống với 2 ví dụ trên.
- Giải thích một chút đoạn code trên, ở đây mình khởi tạo biến message và 1 hàm setMessagelà hàm mặc định màuseStatecung cấp để chúng ta thay đổi statemessage(tương tự như this.setState vậy). Còn giá trị mình truyền vào hàm useState là giá trị khởi tạo mặc định của state message.
- Vậy là chúng ta có thể sử dụng state trong functional component một cách rất dễ dàng rồi. Ngoài ra nếu bạn cần nhiều state hơn thì có thể sử dụng cú pháp tương tự để khai báo
...
const [name, setName] = useState('tunguyen');
const [age, setAge] = useState(40);
...
2.2. Lifecycle method
- 
Ngày trước thì trong functional component chúng ta không thể sử dụng được các lifecycle method của React. 
- 
Hook cũng giúp chúng ta cả thiện các hạn chế này của functional component bằng cách cung cấp cho chúng ta các api như là useEffect.
- 
useEffectđược sử dụng để thay thế cho các lifecycle method nhưcomponentDidMount,componentDidUpdate,componentWillReceiveProps,componentWillUnMount.
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
const Counter = () => {
    useEffect(() => {
        console.log('mounted');
    });
    
    return <p>Hello world</p>;
};
ReactDOM.render(<Counter />, document.getElementById('root'));
- Xem ví dụ trên thì khi chạy ta sẽ thấy log mountedđược in ra sau khi component được render, tương tự như hàmcomponentDidMount.
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
const Counter = () => {
    const [count, setCount] = useState(0);
    
    useEffect(() => {
        console.log('mounted or updated');
    });
    
    return (
        <>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>+</button>
            <button onClick={() => setCount(count - 1)}>-</button>
        </>
    );
};
ReactDOM.render(<Counter />, document.getElementById('root'));
- Ví dụ tiếp theo trên đây thì useEffectđược sử dụng như làcomponentDidUpdate. Mỗi khi hàmsetCountchạy xong là thay đổi giá trị của count thì hàm useEffect đều được trigger.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
  const [count, setCount] = useState(0);
  return <Counter countProps={count} setCountProps={setCount} />;
};
const Counter = (props) => {
  const { countProps, setCountProps } = props;
  useEffect(() => {
    console.log("mounted or updated");
  });
  return (
    <>
      <p>{countProps}</p>
      <button onClick={() => setCountProps(countProps + 1)}>+</button>
      <button onClick={() => setCountProps(countProps - 1)}>-</button>
    </>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));
- 
Ví dụ tiếp theo thì useEffecthoạt đông như làcomponentWillReceiveProps, mỗi khi propcountPropsthay đổi thì hàmuseEffectsẽ được gọi ngay lập tức.
- 
Và useEffectcũng được gọi khi component unmount. Ta sử dụng như sau:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Route, BrowserRouter as Router, Link, Switch } from "react-router-dom";
const App = () => {
  return (
    <Router>
      <Switch>
        <Route path="/counter" component={Counter} />
        <Route path="/home" component={HomePage} />
      </Switch>
    </Router>
  );
};
const HomePage = () => {
  useEffect(() => {
    console.log("homepage mounted or updated");
  });
  useEffect(() => {
    return () => {
      console.log("homepage unmounted");
    };
  }, []);
  return (
    <>
      <Link to="/counter">Go to counter page</Link>
      <p>Home page</p>
    </>
  );
};
const Counter = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("counter mounted or updated");
  });
  useEffect(() => {
    return () => {
      console.log("counter unmounted");
    };
  }, []);
  return (
    <>
      <Link to="/home">Go to homepage</Link>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));
- Chúng ta sẽ viết thêm 1 useEffect khác để phục vụ cho việc khi component unmount, ta sử dụng thêm cú pháp [] để chỉ ra useEffect sẽ được call 1 lần duy nhất khi component unmount.
- Với đoạn code trên khi chúng ta chuyển qua lại giữa counter page và homepage thì trong log sẽ log ra như sau

- Chúng ta còn có thể linh hoạt sử dụng useEffect để tương tác vs prevProps như là componentWillReceivePropsvậy
- Cùng xem ví dụ sau
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
const usePrevious = value => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
const App = () => {
  const [count, setCount] = useState(0);
  return <Counter countProps={count} setCountProps={setCount} />;
};
const Counter = props => {
  const { countProps, setCountProps } = props;
  useEffect(() => {
    console.log("mounted or updated");
  });
  const prevCountProps = usePrevious(countProps);
  return (
    <>
      <p>{countProps}</p>
      <p>{prevCountProps}</p>
      <button onClick={() => setCountProps(countProps + 1)}>+</button>
      <button onClick={() => setCountProps(countProps - 1)}>-</button>
    </>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));
- Hàm usePrevioussử dụng 2 hook rất linh hoạt trong trường hợp này, nó tạo 1 reference để lưu trữ giá trị của count hiện tại sau đó return ra value của nó, để khi useEffect thì giá trị của reference sẽ được gán bằng giá trị của count. Lần sau khi setCount thì nó sẽ trả ra gía trị trước đó của count. Đây là tip khá thú vị mình tìm được trên trang chủ của react.
3. Conclusion
- Trên đây mình đã giới thiệu cũng như đưa ra 1 vài ví dụ về 2 hook useState và useEffect có thể giúp chúng ta biến functional component có thể chạy được y như class component.
- Hiện tại dự án mình đang tham gia cũng đã chuyển qua sử dụng hook và functional component để thay thế cho class component rồi, tương lai chắc chắn hook sẽ được sử dụng rất phổ biến vì sự hiểu quả của nó.
4. References
All rights reserved
 
  
 