React Hooks cơ bản đến nâng cao: useState, useEffect và beyond

React Hooks cơ bản đến nâng cao: useState, useEffect và beyond
Mục lục ẩn

Giới thiệu về React Hooks

Hooks là gì? Vì sao được giới thiệu từ React 16.8

React Hooks là một cơ chế mới được giới thiệu từ phiên bản React 16.8, cho phép bạn sử dụng state và các tính năng của React mà không cần phải viết class component. Trước đây, nếu bạn muốn quản lý state hoặc xử lý vòng đời component, bạn buộc phải dùng class, dẫn đến code phức tạp và khó tái sử dụng logic.

Với Hooks, bạn có thể viết component hoàn toàn bằng function – nhẹ hơn, dễ test hơn và dễ chia sẻ logic hơn.

So sánh với class component lifecycle

Hooks thay thế các lifecycle methods bằng cách trừu tượng hóa thành các hàm như useEffect, cho phép bạn kiểm soát khi nào một logic chạy sau render hoặc khi unmount. Ví dụ:

Khởi đầu website của bạn thật mạnh mẽ, mượt mà với hệ thống hosting cấu hình cao cấp tại AZDIGI.

Class ComponentHooks Equivalent
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [dep])
componentWillUnmountuseEffect(() => { return () => {} }, [])

Hooks giúp gom nhóm logic liên quan với nhau thay vì bị tách rời theo từng lifecycle method như trước.

Lợi ích của Hooks trong phát triển React hiện đại

  • Tăng khả năng tái sử dụng logic qua custom hooks
  • Code ngắn gọn, dễ đọc, không cần dùng this
  • Dễ test và kiểm soát side effect
  • Hỗ trợ tốt cho tư duy functional programming trong UI

useState – Quản lý state đơn giản

Cách sử dụng cơ bản

useState là Hook cơ bản nhất dùng để quản lý state trong functional component:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Bạn đã bấm {count} lần</p>
      <button onClick={() => setCount(count + 1)}>Bấm tôi</button>
    </div>
  );
}

Ở đây, useState(0) trả về một mảng gồm countsetCount. Mỗi lần gọi setCount, component sẽ render lại với giá trị mới.

Cập nhật state bất đồng bộ và batch update

Điều quan trọng cần nhớ là việc cập nhật state là bất đồng bộ. Nếu bạn gọi nhiều lần setState liên tiếp, React có thể batch lại để tối ưu render.

setCount(count + 1);
setCount(count + 1); // Không tăng 2 lần như bạn nghĩ!

Để xử lý đúng, nên dùng function:

setCount(prev => prev + 1);
setCount(prev => prev + 1); // Giờ sẽ tăng đúng 2 lần

Lưu ý khi làm việc với object hoặc array state

Khi state là object hoặc array, cần clone trước khi update:

const [user, setUser] = useState({ name: '', age: 0 });

setUser(prev => ({ ...prev, age: 25 }));

Nếu bạn mutate trực tiếp (ví dụ: user.age = 25), React sẽ không nhận biết thay đổi và không render lại.

useEffect – Quản lý side-effect trong component

Cách hoạt động và thời điểm chạy useEffect

useEffect dùng để xử lý các side effect – ví dụ: gọi API, đăng ký sự kiện, thao tác DOM. Nó chạy sau khi render, và có thể tùy biến qua dependency array:

useEffect(() => {
  console.log("Component mounted hoặc count thay đổi");
}, [count]);

Cleanup function và useEffect với dependency

Khi cần dọn dẹp (clean up) như bỏ đăng ký sự kiện, bạn có thể return một hàm từ useEffect:

useEffect(() => {
  const timer = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(timer);
}, []);

Cleanup sẽ được gọi khi component unmount hoặc dependency thay đổi.

useEffect vs useLayoutEffect: khi nào dùng cái nào?

  • useEffect: chạy sau khi DOM đã vẽ xong => phù hợp với thao tác async hoặc API.
  • useLayoutEffect: chạy ngay trước khi vẽ DOM => dùng khi cần đo kích thước hoặc thao tác layout (scroll, animation).

Nếu không chắc, hãy dùng useEffect – trừ khi bạn có lý do đặc biệt cần đồng bộ hóa với DOM layout.

useRef – Lưu trữ tham chiếu và DOM manipulation

Dùng useRef để truy cập DOM

Hook useRef thường được sử dụng để truy cập trực tiếp tới DOM node trong functional component – thay thế cho createRef() trong class:

import { useRef } from 'react';

function InputFocus() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current?.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

useRef như một biến không gây re-render

Khác với useState, giá trị lưu trong useRef sẽ không gây re-render khi thay đổi. Do đó, nó phù hợp để lưu trữ các giá trị tạm thời, như timeout ID, previous value hoặc đếm số lần render:

const renderCount = useRef(0);
renderCount.current += 1;

So sánh useRef với useState trong một số use case

Mục đíchDùng Hook nào?
Lưu giá trị thay đổi không cần renderuseRef
Gây trigger re-render khi thay đổiuseState
Truy cập DOM trực tiếpuseRef

Tóm lại: Nếu bạn cần lưu giá trị xuyên suốt giữa các lần render mà không cần hiển thị lại giao diện, useRef là lựa chọn nhẹ và hiệu quả.

useMemo và useCallback – Tối ưu hiệu năng

Khi nào cần dùng useMemo / useCallback?

Trong React, mỗi lần component re-render thì tất cả function và object/array sẽ được tạo mới lại, gây ra re-render không cần thiết ở các component con nếu dùng React.memo.

  • useMemo: Ghi nhớ giá trị đã tính toán.
  • useCallback: Ghi nhớ hàm callback đã tạo.

Tránh render lại không cần thiết

Ví dụ với useMemo:

const expensiveValue = useMemo(() => {
  return computeExpensiveValue(data);
}, [data]);

Và với useCallback:

const handleClick = useCallback(() => {
  console.log('clicked');
}, []);

Nhờ vậy, các component con dùng React.memo sẽ không bị re-render khi không cần thiết.

Ví dụ tối ưu một danh sách lớn hoặc component con

const ListItem = React.memo(({ onClick }) => {
  console.log("Render ListItem");
  return <button onClick={onClick}>Click me</button>;
});

function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log("Clicked");
  }, []);

  return (
    <>
      <ListItem onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Tăng count</button>
    </>
  );
}

Nếu không dùng useCallback, mỗi lần App re-render thì handleClick sẽ là hàm mới => ListItem cũng render lại.

useReducer – Quản lý state phức tạp

Khái niệm reducer pattern

useReducer là Hook cho phép bạn quản lý state phức tạp bằng cách áp dụng mô hình reducer – tương tự Redux. Thay vì cập nhật state trực tiếp, bạn dispatch action vào reducer để thay đổi state.

Khi nào nên dùng useReducer thay vì useState?

  • Khi state là object phức tạp, nhiều trường
  • Khi logic cập nhật state rẽ nhánh theo nhiều loại action
  • Khi muốn tổ chức logic rõ ràng, dễ test và scale

Tổ chức logic reducer rõ ràng và testable

Ví dụ đơn giản:

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

Ưu điểm: Giúp tách logic cập nhật state ra khỏi UI, dễ bảo trì và test độc lập.

Custom Hooks – Tái sử dụng logic

Tạo custom hook đơn giản: useWindowSize, useDebounce, useFetch

Custom hook là những function bắt đầu bằng use, có thể gọi các hook khác bên trong, dùng để tái sử dụng logic có state/side-effect.

Ví dụ: custom hook để lấy kích thước cửa sổ trình duyệt:

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

  useEffect(() => {
    const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

Hook này có thể dùng ở mọi component:

const { width, height } = useWindowSize();

Các custom hook phổ biến khác:

  • useDebounce(value, delay)
  • useFetch(url)
  • usePrevious(value)

Quy tắc đặt tên và cách tái sử dụng

  • Tên phải bắt đầu bằng use
  • Có thể dùng các hook khác bên trong
  • Logic phải tách biệt giao diện (không render JSX trong custom hook)

Custom hook giúp giữ component “gọn gàng” và tăng khả năng tái sử dụng.

Chia sẻ logic giữa các component phức tạp

Thay vì copy-paste các đoạn useEffect, useState, bạn chỉ cần viết một lần trong custom hook. Điều này giúp DRY (Don’t Repeat Yourself) và bảo trì dễ hơn.

Ví dụ: nhiều component cần debounce giá trị đầu vào? Viết useDebounce() một lần – dùng mọi nơi.

Các lưu ý, best practices khi dùng Hooks

Quy tắc “Rules of Hooks”

React yêu cầu tuân thủ 2 quy tắc:

  1. Chỉ gọi Hook ở cấp độ cao nhất – không gọi trong vòng lặp, điều kiện, hoặc hàm con.
  2. Chỉ gọi Hook trong function component hoặc custom hook

Sai quy tắc sẽ dẫn đến lỗi runtime hoặc hành vi không xác định.

// Không được làm thế này!
if (someCondition) {
  useEffect(() => {}, []);
}

Debug và kiểm tra hook trong môi trường dev

  • Dùng React Developer Tools để xem giá trị state/hook theo component
  • Dùng eslint-plugin-react-hooks để phát hiện vi phạm rules of hooks
  • Có thể test custom hook với thư viện như @testing-library/react-hooks

Gợi ý thư viện hỗ trợ (react-use, ahooks, …)

Các thư viện cung cấp hàng trăm custom hooks sẵn dùng:

  • react-use: hook quản lý lifecycle, sensor, media, permission, v.v.
  • ahooks: do Alibaba phát triển, mạnh mẽ và nhiều tính năng nâng cao
  • zustand: quản lý global state siêu nhẹ, hook-first

Sử dụng thư viện đúng cách giúp bạn tập trung vào logic business thay vì reinvent the wheel.

Tổng kết

Trong bài viết này, bạn đã đi qua hành trình từ các hook cơ bản như useState, useEffect, đến các hook nâng cao như useReducer, useMemo, useRef, và đặc biệt là custom hook để tái sử dụng logic.

Việc thành thạo React Hooks không chỉ giúp code bạn sạch hơn, gọn hơn, mà còn dễ bảo trì, mở rộng, và phù hợp với xu hướng hiện đại trong phát triển frontend.

Lộ trình tiếp theo

Nếu bạn đã quen với các hook cơ bản, hãy tìm hiểu thêm về:

  • React Query – quản lý server state với hook-based API
  • Zustand, Jotai – quản lý global state không cần boilerplate
  • SWR – hook fetch dữ liệu siêu đơn giản của Vercel

Hooks chỉ là khởi đầu – nhưng là nền móng quan trọng cho hệ sinh thái React hiện đại.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

For security, use of CloudFlare's Turnstile service is required which is subject to the CloudFlare Privacy Policy and Terms of Use.

scroll to top