Skip to content

Instantly share code, notes, and snippets.

@tuannguyen29
Last active March 4, 2026 04:54
Show Gist options
  • Select an option

  • Save tuannguyen29/ee2e854d4bcfead210a1c88ea07808a2 to your computer and use it in GitHub Desktop.

Select an option

Save tuannguyen29/ee2e854d4bcfead210a1c88ea07808a2 to your computer and use it in GitHub Desktop.
Nắm chắc các core concepts của React thông qua lý thuyết ngắn gọn + ví dụ thực tế + project tập code.

⚛️ React Core — Hướng dẫn nhanh cho Junior Frontend

Mục tiêu: Nắm chắc các core concepts của React thông qua lý thuyết ngắn gọn + ví dụ thực tế + project tập code.

Yêu cầu đầu vào: Biết HTML, CSS, JavaScript cơ bản (ES6+).


📚 Mục lục

  1. Functional Component
  2. JSX
  3. Props vs State
  4. useState
  5. useEffect
  6. Re-render Mechanism
  7. Component Lifecycle
  8. Virtual DOM
  9. Bonus: useRef, useCallback, useMemo
  10. 🚀 Project Tập Code

1. Functional Component

Khái niệm

Functional Component là một hàm JavaScript trả về JSX. Đây là cách viết component hiện đại nhất trong React (thay thế Class Component từ React 16.8+).

// ✅ Functional Component — đơn giản, rõ ràng
function Greeting({ name }) {
  return <h1>Xin chào, {name}!</h1>;
}

// Arrow function cũng OK
const Greeting = ({ name }) => <h1>Xin chào, {name}!</h1>;

Quy tắc đặt tên

  • Tên component phải viết hoa chữ cái đầu (PascalCase): UserCard, LoginForm
  • Tên file nên trùng với tên component: UserCard.jsx

💡 Ghi nhớ

Functional Component = hàm nhận vào props → trả ra UI (JSX). Không có gì phức tạp hơn thế.


2. JSX

Khái niệm

JSX (JavaScript XML) là cú pháp cho phép viết HTML-like code bên trong JavaScript. Trình biên dịch (Babel) sẽ chuyển JSX thành React.createElement(...).

// JSX
const element = <h1 className="title">Hello World</h1>;

// Babel biên dịch thành:
const element = React.createElement("h1", { className: "title" }, "Hello World");

Các quy tắc quan trọng

Quy tắc Sai ❌ Đúng ✅
Chỉ return 1 root element return <h1/><p/> return <><h1/><p/></>
Dùng className thay class class="btn" className="btn"
Biểu thức JS dùng {} <p>name</p> <p>{name}</p>
Self-closing tag <input> <input />
Style dùng object style="color:red" style={{ color: 'red' }}
function ProductCard({ product }) {
  return (
    // Fragment <> thay cho div không cần thiết
    <>
      <h2 className="product-name">{product.name}</h2>
      <p style={{ color: product.inStock ? 'green' : 'red' }}>
        {product.inStock ? 'Còn hàng' : 'Hết hàng'}
      </p>
      {/* Render có điều kiện */}
      {product.discount > 0 && <span>-{product.discount}%</span>}
    </>
  );
}

3. Props vs State

So sánh

Props State
Định nghĩa Dữ liệu truyền từ ngoài vào component Dữ liệu nội bộ của component
Ai quản lý Component cha Chính component đó
Có thay đổi được không? ❌ Không (read-only) ✅ Có (dùng setter)
Thay đổi có gây re-render? ✅ Có ✅ Có
// Props — cha truyền xuống, con KHÔNG được sửa
function Button({ label, onClick, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
}

// State — component tự quản lý
function Counter() {
  const [count, setCount] = useState(0); // state nội bộ

  return (
    <div>
      <p>Đếm: {count}</p>
      <Button label="Tăng" onClick={() => setCount(count + 1)} />
    </div>
  );
}

💡 Ghi nhớ

"Props flows down, events bubble up" — Dữ liệu đi từ cha → con qua props. Con muốn báo cha thì gọi callback function được truyền qua props.


4. useState

Khái niệm

useState là hook dùng để lưu trữ và cập nhật dữ liệu trong component. Mỗi lần state thay đổi, component sẽ re-render.

const [state, setState] = useState(initialValue);
//     ↑         ↑               ↑
//  giá trị   hàm cập nhật    giá trị ban đầu

Các pattern thường gặp

function LoginForm() {
  // 1. State đơn giản
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  // 2. State là object
  const [form, setForm] = useState({ username: '', password: '' });

  // 3. Updater function (dùng khi state mới phụ thuộc state cũ)
  const [count, setCount] = useState(0);
  const increment = () => setCount(prev => prev + 1); // ✅ Luôn dùng cách này khi cần prev

  // 4. Cập nhật object state — PHẢI spread để giữ các field khác
  const handleChange = (field) => (e) => {
    setForm(prev => ({ ...prev, [field]: e.target.value })); // ✅
    // setForm({ [field]: e.target.value }); ❌ Sẽ mất các field khác!
  };

  return (
    <form>
      <input
        value={form.username}
        onChange={handleChange('username')}
        placeholder="Username"
      />
      <input
        type="password"
        value={form.password}
        onChange={handleChange('password')}
        placeholder="Password"
      />
    </form>
  );
}

⚠️ Cạm bẫy thường gặp

// ❌ Sai: setState không cập nhật ngay lập tức
const handleClick = () => {
  setCount(count + 1);
  console.log(count); // Vẫn là giá trị CŨ!
};

// ❌ Sai: Mutate state trực tiếp
const addItem = () => {
  items.push(newItem); // KHÔNG BAO GIỜ làm vậy!
  setItems(items);
};

// ✅ Đúng: Tạo array mới
const addItem = () => {
  setItems(prev => [...prev, newItem]);
};

5. useEffect

Khái niệm

useEffect dùng để thực hiện side effects — những việc xảy ra ngoài luồng render như: gọi API, đăng ký event listener, set timeout, thao tác DOM trực tiếp.

useEffect(() => {
  // Code chạy sau mỗi render có dependency thay đổi

  return () => {
    // Cleanup — chạy trước khi effect chạy lại hoặc component unmount
  };
}, [dependency1, dependency2]); // Dependency array

3 dạng sử dụng

// 1. Chạy sau MỖI lần render (hiếm dùng, cẩn thận infinite loop)
useEffect(() => {
  console.log('Component đã render');
});

// 2. Chạy MỘT LẦN khi mount (phổ biến nhất — gọi API lần đầu)
useEffect(() => {
  fetchUserData();
}, []); // Dependency array rỗng

// 3. Chạy khi dependency thay đổi
useEffect(() => {
  fetchProducts(categoryId);
}, [categoryId]); // Chạy lại khi categoryId thay đổi

Ví dụ thực tế: Gọi API + Cleanup

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // AbortController để hủy request nếu component unmount
    const controller = new AbortController();

    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const res = await fetch(`/api/users/${userId}`, {
          signal: controller.signal
        });
        if (!res.ok) throw new Error('Lỗi tải dữ liệu');
        const data = await res.json();
        setUser(data);
      } catch (err) {
        if (err.name !== 'AbortError') { // Bỏ qua lỗi do cleanup
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUser();

    // Cleanup: hủy request khi userId thay đổi hoặc unmount
    return () => controller.abort();
  }, [userId]);

  if (loading) return <p>Đang tải...</p>;
  if (error) return <p>Lỗi: {error}</p>;
  return <div>{user?.name}</div>;
}

⚠️ Cạm bẫy: Infinite Loop

// ❌ Infinite loop — object/array mới được tạo mỗi lần render
const [filters, setFilters] = useState({ page: 1 });

useEffect(() => {
  fetchData(filters);
}, [filters]); // filters là object mới mỗi render → chạy mãi!

// ✅ Giải pháp: dùng giá trị primitive làm dependency
const [page, setPage] = useState(1);

useEffect(() => {
  fetchData({ page });
}, [page]); // primitive → so sánh được

6. Re-render Mechanism

Khi nào component re-render?

React sẽ re-render component khi:

  1. State thay đổi (useState setter được gọi)
  2. Props thay đổi (component cha truyền props mới)
  3. Component cha re-render (dù props con không đổi — đây là điểm nhiều người bỏ qua!)
  4. Context thay đổi (useContext)
ParentComponent re-renders
    ↓
ChildComponent cũng re-renders (dù props không đổi!)
    ↓
GrandChildComponent cũng re-renders

Visualize re-render

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

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Tăng ({count})</button>
      <Child /> {/* ← Re-render mỗi khi Parent re-render, dù không nhận props gì! */}
    </div>
  );
}

function Child() {
  console.log('Child render'); // Sẽ log mỗi lần Parent re-render
  return <p>Tôi là child</p>;
}

Tối ưu re-render với React.memo

// React.memo: chỉ re-render khi props thực sự thay đổi
const Child = React.memo(function Child({ name }) {
  console.log('Child render');
  return <p>Xin chào {name}</p>;
});

// ⚠️ Lưu ý: nếu props là function/object, cần kết hợp useCallback/useMemo

Batching — React gộp nhiều setState

// React 18+: tự động batch cả trong async
const handleClick = async () => {
  // React sẽ BATCH 3 setState này → chỉ re-render 1 lần
  setLoading(true);
  setData(await fetchData());
  setLoading(false);
};

7. Component Lifecycle

Lifecycle trong Functional Component

MOUNT               UPDATE              UNMOUNT
  │                   │                   │
  ▼                   ▼                   ▼
render()          render()           cleanup()
  │                   │               (return của
  ▼                   ▼                useEffect)
useEffect()       useEffect()
([] - một lần)    (khi dep thay đổi)
function LifecycleDemo({ id }) {
  const [data, setData] = useState(null);

  // ===== MOUNT (componentDidMount) =====
  useEffect(() => {
    console.log('✅ Component mounted');
    // Khởi tạo: gọi API, đăng ký event, set timer...
    const timer = setInterval(() => console.log('tick'), 1000);

    // ===== UNMOUNT (componentWillUnmount) =====
    return () => {
      console.log('🔴 Component unmounted');
      clearInterval(timer); // Dọn dẹp!
    };
  }, []);

  // ===== UPDATE (componentDidUpdate) =====
  useEffect(() => {
    console.log('🔄 id thay đổi:', id);
    fetchData(id).then(setData);
  }, [id]); // Chạy lại khi id thay đổi

  // ===== BEFORE RENDER (render phase) =====
  // Không có side effects ở đây!
  return <div>{data?.name}</div>;
}

Mapping từ Class → Functional

Class Component Functional Component (Hooks)
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {}, [dep])
componentWillUnmount return () => {} trong useEffect
shouldComponentUpdate React.memo
getDerivedStateFromProps Tính toán trực tiếp trong render

8. Virtual DOM

Virtual DOM là gì?

Virtual DOM là bản copy nhẹ (JavaScript object) của Real DOM. Thay vì thao tác trực tiếp vào DOM (chậm), React thao tác trên Virtual DOM (nhanh) rồi tính toán sự khác biệt.

State thay đổi
    │
    ▼
React tạo Virtual DOM MỚI
    │
    ▼
So sánh (Diffing/Reconciliation) với Virtual DOM CŨ
    │
    ▼
Tính ra các thay đổi tối thiểu (Patches)
    │
    ▼
Chỉ cập nhật những phần thay đổi vào Real DOM

Tại sao quan trọng?

// ❌ Không dùng React — cập nhật cả list dù chỉ thêm 1 item
document.getElementById('list').innerHTML = items.map(i => `<li>${i}</li>`).join('');

// ✅ React — chỉ thêm 1 <li> mới vào DOM thực
setItems(prev => [...prev, newItem]);

Key prop — Giúp React Diff nhanh hơn

// ❌ Dùng index làm key — gây bug khi sort/delete
{items.map((item, index) => (
  <li key={index}>{item.name}</li>
))}

// ✅ Dùng ID duy nhất làm key
{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

Giải thích: Khi bạn xóa item giữa list, nếu dùng index làm key, React sẽ nghĩ item đầu tiên không thay đổi (vẫn key=0) và update sai. Dùng id duy nhất giúp React track đúng từng item.


9. Bonus: useRef, useCallback, useMemo

useRef — Giữ giá trị mà KHÔNG gây re-render

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null); // Lưu interval ID, không gây re-render

  const start = () => {
    intervalRef.current = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
  };

  const stop = () => {
    clearInterval(intervalRef.current);
  };

  // useRef cũng dùng để truy cập DOM element trực tiếp
  const inputRef = useRef(null);
  const focusInput = () => inputRef.current.focus();

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
      <p>{seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  );
}

useCallback — Memoize function

// Dùng khi truyền function xuống component con được wrap bởi React.memo
const handleDelete = useCallback((id) => {
  setItems(prev => prev.filter(item => item.id !== id));
}, []); // Chỉ tạo lại function khi dependency thay đổi

useMemo — Memoize giá trị tính toán nặng

// Chỉ tính lại khi items hoặc filter thay đổi
const filteredItems = useMemo(() => {
  return items.filter(item => item.category === filter)
              .sort((a, b) => b.price - a.price);
}, [items, filter]);

10. 🚀 Project Tập Code

Project 1 — Todo App (Covers: useState, Props, Re-render, JSX)

todo-app/
├── src/
│   ├── components/
│   │   ├── TodoInput.jsx    ← Nhận input, gọi callback prop
│   │   ├── TodoItem.jsx     ← Hiển thị 1 todo, toggle, delete
│   │   └── TodoList.jsx     ← Render list TodoItem
│   ├── App.jsx              ← Quản lý state todos[]
│   └── main.jsx

Yêu cầu:

  • Thêm todo mới
  • Đánh dấu todo hoàn thành / chưa hoàn thành
  • Xóa todo
  • Filter: All / Active / Completed
  • Đếm số todo còn lại

Mở rộng: Lưu todos vào localStorage bằng useEffect


Project 2 — GitHub User Search (Covers: useEffect, useState, API call, Lifecycle)

github-search/
├── src/
│   ├── components/
│   │   ├── SearchBar.jsx    ← Input tìm kiếm với debounce
│   │   ├── UserCard.jsx     ← Hiển thị thông tin user
│   │   └── RepoList.jsx     ← Danh sách repo của user
│   ├── hooks/
│   │   └── useDebounce.js   ← Custom hook debounce
│   ├── App.jsx
│   └── main.jsx

Yêu cầu:

  • Tìm kiếm user GitHub qua API: https://api.github.com/users/{username}
  • Debounce input 500ms (tránh gọi API liên tục)
  • Hiển thị: avatar, tên, bio, số followers, số repos
  • Hiển thị danh sách repos (gọi API thứ 2)
  • Xử lý loading state và error state
  • Cleanup AbortController khi username thay đổi

Project 3 — Shopping Cart (Covers: Tất cả core + useCallback, useMemo)

shopping-cart/
├── src/
│   ├── components/
│   │   ├── ProductGrid.jsx  ← Danh sách sản phẩm
│   │   ├── ProductCard.jsx  ← 1 sản phẩm (React.memo)
│   │   ├── Cart.jsx         ← Giỏ hàng
│   │   └── CartItem.jsx     ← 1 item trong giỏ
│   ├── App.jsx
│   └── main.jsx

Yêu cầu:

  • Fetch danh sách products từ https://fakestoreapi.com/products
  • Thêm/xóa sản phẩm vào giỏ
  • Thay đổi số lượng từng item
  • Tính tổng tiền (dùng useMemo)
  • Filter theo category
  • Giỏ hàng lưu vào localStorage
  • Tối ưu re-render với React.memo + useCallback

📋 Checklist Tự Kiểm Tra

Hoàn thành các project xong, hãy đảm bảo bạn trả lời được:

  • Sự khác biệt giữa propsstate là gì?
  • Khi nào dùng useEffect với [] và khi nào có dependency?
  • Tại sao KHÔNG được mutate state trực tiếp?
  • Tại sao phải có key khi render list?
  • useRef khác useState ở điểm gì?
  • Khi nào nên dùng React.memo, useCallback, useMemo?
  • Virtual DOM giúp React tối ưu performance như thế nào?

📖 Tài nguyên thêm

Tài nguyên Link Ghi chú
React Docs (Official) https://react.dev Tài liệu chính thức, rất tốt
Beta Docs Tutorial https://react.dev/learn Học theo project
React DevTools Chrome Extension Debug re-render trực quan
Fake API https://jsonplaceholder.typicode.com API giả cho practice
Fake Store API https://fakestoreapi.com API sản phẩm cho project 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment