SydexaSydexa
Trang chủKhoá họcBài viết
2 Năm Đi Làm React Chỉ Biết useState và useEffect: Bài Học Xương Máu Về Tư Duy Tối Ưu Hóa
Frontend

2 Năm Đi Làm React Chỉ Biết useState và useEffect: Bài Học Xương Máu Về Tư Duy Tối Ưu Hóa

Nguyen Anh Tuan
|
27/08/2025
|
9 phút đọc

Đi làm React 2 năm trời mà chỉ biết mỗi useState và useEffect.

image.png

Anh em cho mình xin một upvote để có động lực viết bài nha ^^

Nghe có vẻ khó tin, nhưng đó là câu chuyện có thật của mình.

Hồi mới đi làm, với mình, React chỉ xoay quanh hai "người bạn thân" này. Thỉnh thoảng lắm, khi cần tương tác trực tiếp với DOM, mình mới dùng đến useRef. Mình cũng không có nhu cầu tìm hiểu thêm các hooks khác. Logic của mình lúc đó rất đơn giản: "Xong việc là được rồi, website chạy đúng chức năng khách hàng yêu cầu là thành công rồi, tìm hiểu thêm làm gì cho mệt."

Và hệ quả tất yếu đã đến. Nhiều lúc ứng dụng mình làm ra chạy được, nhưng nó cứ chậm một cách khó hiểu. Mình không tài nào tìm ra lý do, và chỉ biết tặc lưỡi đổ tại: "Chắc do thư viện này nặng", "Chắc React bị lỗi ở phiên bản này"...

Chính vì tư duy "chạy được là được" và sự thiếu tò mò đó, mà trình độ và mức lương của mình cứ dậm chân tại chỗ suốt hai năm trời. Mình loay hoay trong cái vòng luẩn quẩn của chính mình, không tìm thấy điểm nào để bứt phá.

Sau một thời gian dài, mình mới nhận ra vấn đề không nằm ở React, mà nằm ở chính kiến thức nông cạn của mình. React đã cung cấp cho chúng ta một bộ công cụ cực kỳ mạnh mẽ, và việc của lập trình viên là phải biết dùng đúng công cụ cho đúng việc.

Mẹo hay: Cách học tốt nhất là được học từ những chuyên gia giỏi. Nếu bạn muốn nâng cao kỹ năng React và tối ưu hiệu năng, hãy tham khảo khoá học React Nâng Cao - Chuyên sâu tối ưu hiệu năng của chị Trương Hoài - một kỹ sư phần mềm với nhiều năm kinh nghiệm làm việc tại Singapore.
https://sydexa.com/react-nang-cao-toi-uu-hieu-nang

Trong bài viết này, mình muốn chia sẻ lại những kinh nghiệm xương máu rút ra được từ 2 năm "lạc lối" đó. Đây là những hooks quan trọng trong React, không chỉ để hoàn thành công việc, mà còn để tối ưu hóa hiệu năng ứng dụng, giúp bạn viết code sạch hơn, chuyên nghiệp hơn. Hy vọng những chia sẻ này sẽ có ích cho mọi người, đặc biệt là những bạn đang ở trong hoàn cảnh giống mình trước đây.

Vấn Đề Cốt Lõi: Kẻ Thù Thầm Lặng Của Hiệu Năng

Trước khi đi vào các hooks cụ thể, chúng ta cần hiểu "kẻ thù" lớn nhất của hiệu năng trong React là gì. Đó chính là sự tái re-render không cần thiết.

Khi state hoặc props của một component thay đổi, React sẽ re-render component đó và tất cả các component con của nó. Vấn đề là, nhiều khi các component con không hề nhận được dữ liệu mới, nhưng chúng vẫn bị "vạ lây" và phải render lại. Đây chính là nguồn cơn gây ra sự chậm chạp cho ứng dụng.

"Bộ ba" hooks dưới đây sẽ giúp chúng ta giải quyết triệt để vấn đề này.


1. useMemo: Khi Bạn Cần Ghi Nhớ Một Giá Trị "Đắt Đỏ"

Hãy tưởng tượng bạn có một hàm tính toán rất phức tạp, ví dụ như lọc và sắp xếp một danh sách hàng ngàn sản phẩm. Nếu bạn đặt phép tính này trực tiếp trong component, nó sẽ phải chạy lại mỗi khi component re-render, dù cho danh sách sản phẩm không hề thay đổi. Lãng phí quá phải không?

useMemo được sinh ra để giải quyết việc này.

  • Nó làm gì? useMemo sẽ "ghi nhớ" (memoize) kết quả của một phép tính. Nó chỉ tính toán lại khi một trong các "phụ thuộc" (dependencies) mà bạn chỉ định thay đổi.
  • Khi nào dùng? Khi bạn có những phép tính tốn nhiều tài nguyên (lọc/sắp xếp mảng lớn, tính toán dữ liệu phức tạp).

Ví dụ:

// TRƯỚC KHI DÙNG useMemo
function ProductList({ products, filter }) {
  // Hàm này sẽ chạy lại MỖI KHI ProductList re-render,
  // kể cả khi chỉ có một state không liên quan thay đổi.
  const visibleProducts = filterAndSortProducts(products, filter);

  return <ul>...</ul>;
}

// SAU KHI DÙNG useMemo
import { useMemo } from 'react';

function ProductList({ products, filter }) {
  // visibleProducts chỉ được tính toán lại khi `products` hoặc `filter` thay đổi.
  const visibleProducts = useMemo(() => {
    return filterAndSortProducts(products, filter);
  }, [products, filter]); // <-- Mảng phụ thuộc

  return <ul>...</ul>;
}

2. useCallback: Người Bạn Đồng Hành Của React.memo

Tương tự useMemo nhưng useCallback dùng để "ghi nhớ" một hàm thay vì một giá trị.

"Tại sao phải ghi nhớ một hàm? Nó có tốn tài nguyên đâu?" - Mình đã từng nghĩ vậy.

Vấn đề nằm ở chỗ: mỗi lần component cha re-render, mọi hàm được định nghĩa bên trong nó sẽ được tạo lại từ đầu. Điều này có nghĩa là, dù code của hàm y hệt nhau, nhưng trong mắt JavaScript, chúng là hai hàm khác nhau ở hai vùng nhớ khác nhau.

Khi bạn truyền hàm này xuống component con dưới dạng prop, component con sẽ thấy prop của nó đã "thay đổi" và buộc phải re-render, kể cả khi bạn đã dùng React.memo cho nó.

  • Nó làm gì? useCallback trả về một phiên bản được ghi nhớ của hàm, và chỉ tạo lại hàm mới khi dependencies của nó thay đổi.
  • Khi nào dùng? Khi bạn muốn truyền một hàm callback xuống một component con đã được tối ưu bằng React.memo.
import { useState, useCallback } from 'react';
import { memo } from 'react';

// Component con được tối ưu bằng React.memo
const MyButton = memo(({ onClick }) => {
  console.log("Button re-rendered!");
  return <button onClick={onClick}>Click me</button>;
});

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

  // Mỗi lần Counter re-render, handleClick sẽ là một hàm MỚI
  // const handleClick = () => {
  //   console.log("Clicked!");
  // };

  // Dùng useCallback, handleClick sẽ chỉ được tạo một lần duy nhất
  // vì mảng phụ thuộc [] là rỗng.
  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []); // <-- Không phụ thuộc vào gì cả

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <MyButton onClick={handleClick} />
    </div>
  );
}

Trong ví dụ trên, nếu không có useCallback, mỗi lần bạn nhấn nút "Increment", Counter sẽ re-render, tạo ra một hàm handleClick mới và MyButton sẽ bị re-render một cách vô ích. Với useCallback, MyButton sẽ không bao giờ re-render lại.


3. useReducer: Khi useState Trở Nên Quá Tải

useState rất tuyệt cho những state đơn giản. Nhưng khi logic state của bạn trở nên phức tạp (ví dụ: state tiếp theo phụ thuộc vào state trước đó, hoặc có nhiều hành động cập nhật state khác nhau), component của bạn sẽ sớm trở thành một mớ hỗn độn với hàng loạt hàm set... khác nhau.

useReducer là một giải pháp thay thế mạnh mẽ cho useState trong những trường hợp này.

  • Nó làm gì? useReducer giúp bạn gom tất cả logic cập nhật state vào một nơi duy nhất gọi là "reducer function".
  • Khi nào dùng?
    • Khi state có logic phức tạp.
    • Khi state tiếp theo phụ thuộc vào state trước đó.
    • Khi bạn muốn tối ưu hóa hiệu năng bằng cách truyền dispatch xuống các component con thay vì nhiều hàm callback.

Ví dụ một bộ đếm đơn giản:

import { useReducer } from 'react';

const initialState = { count: 0 };

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() {
  // state là state hiện tại, dispatch là hàm để gửi "hành động"
  const [state, dispatch] = useReducer(reducer, initialState);

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

Nhìn xem, logic increment và decrement đều nằm gọn trong hàm reducer, component Counter chỉ việc gọi dispatch với hành động tương ứng. Rất sạch sẽ và dễ quản lý!


Lời Kết

Nhìn lại chặng đường 2 năm đầu, mình thấy vừa tiếc nuối nhưng cũng vừa biết ơn. Tiếc vì đã lãng phí thời gian, nhưng biết ơn vì chính những "vấp ngã" đó đã cho mình bài học sâu sắc về tầm quan trọng của việc học hỏi không ngừng.

useState và useEffect là nền tảng, là cánh cửa đưa bạn vào thế giới React. Nhưng để xây dựng nên những ứng dụng vững chắc, hiệu năng cao và có thể mở rộng, bạn cần phải nắm vững toàn bộ bộ công cụ mà React cung cấp. useMemo, useCallback, useReducer và cả useContext (để tránh prop-drilling) chính là những gì phân biệt một lập trình viên "làm được việc" và một lập trình viên "làm tốt việc".

Đừng trở thành "mình" của hai năm về trước. Hãy tò mò, hãy đặt câu hỏi "Tại sao ứng dụng lại chậm?", và hãy tìm cách làm cho nó tốt hơn. Đó chính là con đường để bạn không chỉ nâng cao kỹ năng mà còn cả giá trị của bản thân trong ngành lập trình.

Bạn đã có câu chuyện tương tự chưa? Hook "ruột" giúp bạn tối ưu hóa hiệu năng là gì? Hãy chia sẻ ở phần bình luận bên dưới nhé

Bài viết liên quan

Đừng đổ lỗi cho tối ưu hiệu năng nữa: Ứng dụng của bạn giật lag, có thể là do thiết kế sản phẩm quá tệ
Frontend
17/03/2026 · 8 phút đọc

Đừng đổ lỗi cho tối ưu hiệu năng nữa: Ứng dụng của bạn giật lag, có thể là do thiết kế sản phẩm quá tệ

Chắc hẳn mỗi khi chúng ta code xong một trang web và deploy lên production thì có một điều chúng ta khá quan tâm là Tối ưu hiệu năng (Performance Optimization). Trên mạng có nhiều bài viết chứa những thuật ngữ nghe rất đao to búa lớn: * Sử dụng Virtual List (danh sách ảo) để tối ưu việc render danh sách dài... * Sử dụng Web Worker để đẩy các tính toán phức tạp ra khỏi luồng chính (main thread)... * Sử dụng WASM để viết lại thuật toán lõi... Sau khi viết vài bài về tối ưu hiệu năng, cũng nh

Chi tiết bài viết
Chuyện gì thực sự xảy ra ở trình duyệt sau khi nhấn F5?
Frontend
11/03/2026 · 11 phút đọc

Chuyện gì thực sự xảy ra ở trình duyệt sau khi nhấn F5?

Trong lập trình frontend, "làm mới trang" trông có vẻ là một thao tác đơn giản, nhưng ẩn sau đó là một luồng phối hợp phức tạp giữa giao tiếp mạng (network communication), chiến lược cache (caching strategy) và engine render (rendering engine). Đặc biệt là khi nhấn F5, nó không phải là "tải lại hoàn toàn", cũng không phải là "dùng trực tiếp cache ở local", mà là một cơ chế xác thực thông minh nằm giữa hai trạng thái này. Để thực sự làm chủ các kỹ năng tối ưu hóa hiệu năng và debug frontend, bạn

Chi tiết bài viết
Server DTO: Nên giữ nguyên hay chuyển đổi?
Frontend
04/03/2026 · 12 phút đọc

Server DTO: Nên giữ nguyên hay chuyển đổi?

Trong các dự án mà Backend và Frontend được phát triển hoàn toàn tách biệt, chắc hẳn không ít lần chúng ta phải đau đầu với câu hỏi: "Dữ liệu server trả về thì nên xài luôn hay phải xào nấu lại?". Nếu dạo quanh các diễn đàn công nghệ hay đọc các bài thảo luận trên mạng, bạn sẽ thấy cuộc tranh luận về cách xử lý dữ liệu từ API này dường như chưa bao giờ có hồi kết. Cốt lõi của vấn đề luôn xoay quanh hai luồng quan điểm cực kỳ quen thuộc: "Dữ liệu backend trả về sao thì cứ tận dụng nguyên xi như

Chi tiết bài viết
Đừng lạm dụng useMemo nữa! Đây mới là Best Practice chuẩn xác dành cho bạn
Frontend
25/02/2026 · 12 phút đọc

Đừng lạm dụng useMemo nữa! Đây mới là Best Practice chuẩn xác dành cho bạn

useMemo là một React Hook giúp chúng ta ghi nhớ kết quả tính toán giữa các lần render để tối ưu hiệu năng trong một số trường hợp. Tuy nhiên, có một thực trạng đang diễn ra: người không biết thì chẳng bao giờ dùng, còn người đã biết thì lại lạm dụng nó ở mọi lúc, mọi nơi. Bài viết này sẽ giúp bạn hiểu rõ thực sự thì trong những trường hợp nào chúng ta mới nên sử dụng useMemo. Cú pháp tổng quát: useMemo hoạt động như thế nào? Ở lần render đầu tiên, useMemo sẽ thực thi hàm calculateValue (khôn

Chi tiết bài viết
Sydexa
Sydexa
SydexaSydexa

Nền tảng học lập trình với animation trực quan và bài tập tương tác, giúp bạn hiểu sâu thay vì chỉ đọc lý thuyết.

Sydexa FanpageBackend GroupFrontend Group

We learn, We share, We grow

Powered By
Cộng đồng System Design Việt Nam

contact@sydexa.com
0971489013