Kiểu cú pháp dùng React Component cũng tương đối hay khi đối tượng hoá tương đối tốt và đưa ra được một vòng đời tương đối rõ ràng cho một component. Tuy nhiên nó cũng có những vấn đề nhất định khi rất nhiều các lập trình viên FE lại xuất thân từ html, css, javascript, jquery nên họ lập trình với hàm tương đối nhiều và không hẳn là đã quen với lập trình hướng đối tượng. Ngoài ra cũng có những tranh luận không có hồi kết giữa hai tư tưởng lập trình hướng hàm và hướng đối tượng nên có lẽ sự ra đời của React hook cũng giải quyết được các vấn đề mà mình vừa nêu. Trong bài này chúng ta sẽ cùng nhau tìm hiểu về React hook nhé.

Khởi tạo dự án

Đầu tiên chúng ta sẽ khởi tạo dự án react-hook-example bằng cách sử dụng câu lệnh:

yarn create vite react-hook-example

Bạn đừng quên lựa chọn framework là ReactJS và ngôn ngữ là Typescript nhé.
Sau khi bạn khởi tạo dự án xong hãy cd vào thư mục react-hook-example vừa được tạo và gõ lệnh yarn để tải các thư viện cần thiết và chạy lệnh yarn dev để khởi chạy server và bạn có thể truy cập vào trang web thông qua địa chỉ: http://localhost:5173/
Chúng ta sẽ cần thay đổi mã nguồn tập tin main.tsx như sau:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.Fragment>
    <App />
  </React.Fragment>,
)

Hook là gì?

Theo định nghĩa trong tài liệu chính thức của ReactJS thì Hooks là các hàm cho phép bạn “móc vào” các tính năng trạng thái và vòng đời của React từ các function component. Hooks không hoạt động bên trong các lớp — chúng cho phép bạn sử dụng React mà không cần dùng đến các lớp. (Team react không khuyến khích bạn viết lại các component hiện có ngay lập tức nhưng bạn có thể bắt đầu sử dụng Hooks trong các component mới nếu muốn.)
Còn sâu hơn về chi tiết kỹ thuật bên trong của Hooks là gì chúng ta hãy để dành cho bài khác nhé.
Một số hàm mà hook cung cấp sẵn đó là:

  1. useState
  2. useEffect
  3. useContext
  4. useRef
  5. useReducer
  6. useCallback
  7. useMemo

useState

Hàm này chúng ta đã sử dụng để tạo ra một biến trạng thái trước đó rồi, cú pháp của nó có dạng:

const [count, setCount] = useState(0)
const [messages, setMessages] = useState<Array<string>>();

Và khi bạn sử dụng sẽ dùng hàm setter kiểu:

setCount((count) => count + 1)
setMessages(["1", "2", "3"])

useEffect

Hàm này được gọi sau khi render, nó có khả năng theo dõi sự thay đổi của các biến trạng thái, và hàm callback khi component bị unmount.
Ví kiểu này:

import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState<Array<string>>();
  console.log("1. init");

  useEffect(() => {
    console.log("3. useEffect");
  }, []);

  console.log("2. render");
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <button onClick={() => setMessages(["1", "2", "3"])}>
          messages are {messages}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

Thì useEffect sẽ chỉ được gọi 1 lần duy nhất sau khi render xong và chúng ta sẽ nhận được kết quả log là:

1. init
2. render
3. useEffect

Vậy nên bạn sử dụng useEffect thì chưa chắc đã hợp lý, thay vào đó bạn nên gọi API để lấy dữ liệu sẽ hợp lý hơn.
Để theo dõi trạng thái của một biến bạn có thể sử dụng cú pháp sau:

  useEffect(() => {
    console.log("3. useEffect");
  }, [count]);

Thì mỗi lần biến count thay đổi trạng thái thì hàm callback của useEffect sẽ được gọi.
Để có thể clean được các dữ liệu không còn dùng đến hoặc các sự kiện không còn cần được theo dõi thì chúng ta có thể sử dụng useEffect kiểu như sau:

import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Next from './Next';

function App() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState<Array<string>>();
  const [next, setNext] = useState(false);
  console.log("1. init");

  useEffect(() => {
    return () => {
      console.log("unsubscribe events")
    };
  }, [next]);

  useEffect(() => {
    console.log("3. useEffect, count = ", count);
    if (count == 3) {
      setNext(true);
    }
  }, [count]);

  console.log("2. render");
  if (next) {
    console.log("next");
    return <Next />
  }
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <button onClick={() => setMessages(["1", "2", "3"])}>
          messages are {messages}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

Với màn hình Next.tsx có mã nguồn như sau:

import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function Next() {
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default Next

Khi count được tăng lên 3 thì màn hình App sẽ bị unmount và dòng “unsubscribe events” sẽ được in ra. Cụ thể log thực tế sẽ như sau:

1. init
2. render
3. useEffect, count =  0
1. init
2. render
3. useEffect, count =  1
1. init
2. render
3. useEffect, count =  2
1. init
2. render
3. useEffect, count =  3
1. init
2. render
next
unsubscribe events

Những cạm bẫy khi sử dụng useEffect.
Quên không truyền danh sách biến trạng thái, ví dụ:

 useEffect(() => {
    console.log("unsubscribe events");
  });

Kiểu như trên thì khi có bất kỳ trạng thái nào thay đổi thì “unsubscribe events” sẽ được in ra. Ít nhất bạn cũng cần phải truyền một mảng rỗng để useEffect chỉ được gọi duy nhất 1 lần như dưới đây:

 useEffect(() => {
    console.log("unsubscribe events");
  }, []);

Sử dụng sai hàm clean, ví dụ:

useEffect(() => {
    console.log("3. useEffect, count = ", count);
    if (count == 3) {
      setNext(true);
    }
    return () => {
      console.log("unsubscribe events");
    }
  }, [count]);

Nếu bạn nghĩ rằng “unsubscribe events” sẽ chỉ được gọi 1 lần duy nhất khi view bị umount thì bạn đã nhầm, nó sẽ được gọi mỗi lần biến count thay đổi và rất có thể các biến cần phải duy trì hoặc các sự kiện cần lắng nghe sẽ bị huỷ và bạn sẽ gặp lỗi.

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu về 2 hàm useState và useEffect của React Hooks, các hàm còn lại chúng ta sẽ tìm hiểu trong bài sau nhé.


Cám ơn bạn đã quan tâm đến bài viết này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a