📌

Giới Thiệu React

React là gì?

React là thư viện JavaScript được phát triển bởi Facebook (Meta) từ năm 2013. React cho phép xây dựng giao diện người dùng (UI) theo mô hình component-based, giúp code dễ quản lý và tái sử dụng.

💡 Tại sao chọn React?
• Thư viện UI phổ biến nhất thế giới
• Virtual DOM - hiệu suất cao
• Component-based - code tái sử dụng tốt
• Hệ sinh thái khổng lồ (Next.js, React Native)
• Hooks - quản lý state hiện đại và mạnh mẽ

Cài đặt React

# Tạo project mới với Vite (khuyên dùng)
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

# Hoặc dùng Create React App
npx create-react-app my-app
cd my-app
npm start

Hello World với React

import { useState } from 'react';

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

    return (
        <div>
            <h1>Xin chào, React! ⚛️</h1>
            <p>Bạn đã nhấn {count} lần</p>
            <button onClick={() => setCount(count + 1)}>
                Nhấn vào đây
            </button>
        </div>
    );
}

export default App;

Giải thích:
useState(0): Tạo state với giá trị ban đầu là 0
setCount: Hàm cập nhật state
• JSX: Cú pháp HTML trong JavaScript

📚

Khóa Học React Từ A Đến Z (25 Bài)

🎯 Khóa học toàn diện! Từ cơ bản JSX đến tất cả React Hooks (bao gồm useLayoutEffect, useInsertionEffect), Testing, Performance Optimization và dự án thực tế.
🪝

React Hooks Overview

📘 Hooks trong React

Hooks cho phép bạn sử dụng state và các tính năng React khác mà không cần viết class component. Được giới thiệu từ React 16.8.

useState - Quản lý State

Hook cơ bản nhất, cho phép component function có state riêng.

import { useState } from 'react';

function Counter() {
    // State đơn giản
    const [count, setCount] = useState(0);

    // State với object
    const [user, setUser] = useState({
        name: 'An',
        age: 25
    });

    // Functional update (khi state mới phụ thuộc state cũ)
    const increment = () => {
        setCount(prev => prev + 1);
    };

    // Update object (phải spread toàn bộ)
    const updateName = (newName) => {
        setUser(prev => ({ ...prev, name: newName }));
    };

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>+1</button>
            <p>User: {user.name}, {user.age} tuổi</p>
        </div>
    );
}

useEffect - Side Effects

Xử lý side effects: gọi API, subscriptions, DOM manipulation.

import { useState, useEffect } from 'react';

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

    useEffect(() => {
        // Side effect: fetch data
        const fetchUser = async () => {
            setLoading(true);
            const res = await fetch(`/api/users/${userId}`);
            const data = await res.json();
            setUser(data);
            setLoading(false);
        };

        fetchUser();

        // Cleanup function (chạy khi unmount hoặc dependency thay đổi)
        return () => {
            console.log('Cleanup!');
        };
    }, [userId]); // Dependency array - chỉ re-run khi userId thay đổi

    if (loading) return <p>Đang tải...</p>;
    return <h2>{user?.name}</h2>;
}
💡 Quy tắc Dependency Array:
[]: Chạy 1 lần khi mount
[a, b]: Chạy khi a hoặc b thay đổi
• Không có: Chạy mỗi lần render

useLayoutEffect vs useEffect

useLayoutEffect chạy đồng bộ sau khi DOM cập nhật nhưng trước khi browser paint. Dùng khi cần đo lường DOM hoặc ngăn flash.

import { useLayoutEffect, useRef, useState } from 'react';

function Tooltip({ text, children }) {
    const ref = useRef(null);
    const [position, setPosition] = useState({ top: 0, left: 0 });

    useLayoutEffect(() => {
        // Đo DOM TRƯỚC KHI browser paint
        // → Không bị "nhấp nháy" (flash)
        const rect = ref.current.getBoundingClientRect();
        setPosition({
            top: rect.top - 30,
            left: rect.left + rect.width / 2
        });
    }, [text]);

    return (
        <div ref={ref}>
            {children}
            <span style={{ position: 'fixed', ...position }}>
                {text}
            </span>
        </div>
    );
}

// ⚠️ useEffect: render → paint → effect (có thể thấy flash)
// ✅ useLayoutEffect: render → effect → paint (không flash)
⚠️ Lưu ý: useLayoutEffect chặn browser paint, nên chỉ dùng khi thực sự cần. Nếu không cần đo DOM, hãy dùng useEffect.

Best Practices

Quy tắc sử dụng Hooks

• Chỉ gọi Hooks ở top level (không trong if/for/nested functions)
• Chỉ gọi trong React function components hoặc custom hooks
• Đặt tên custom hooks bắt đầu bằng use (VD: useFetch)
• Luôn khai báo đầy đủ dependency array

Custom Hook - useFetch

import { useState, useEffect } from 'react';

// Custom hook tái sử dụng cho fetch data
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const controller = new AbortController();

        const fetchData = async () => {
            try {
                setLoading(true);
                const res = await fetch(url, {
                    signal: controller.signal
                });
                if (!res.ok) throw new Error('Fetch failed');
                const json = await res.json();
                setData(json);
            } catch (err) {
                if (err.name !== 'AbortError') {
                    setError(err.message);
                }
            } finally {
                setLoading(false);
            }
        };

        fetchData();

        // Cleanup: hủy request nếu component unmount
        return () => controller.abort();
    }, [url]);

    return { data, loading, error };
}

// Sử dụng
function UserList() {
    const { data, loading, error } = useFetch('/api/users');

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

    return (
        <ul>
            {data.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}
🌍

Ứng Dụng Thực Tế

Component có State phức tạp

import { useReducer, useCallback } from 'react';

// Reducer pattern cho state phức tạp
const initialState = { items: [], filter: 'all', loading: false };

function todoReducer(state, action) {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                ...state,
                items: [...state.items, {
                    id: Date.now(),
                    text: action.payload,
                    completed: false
                }]
            };
        case 'TOGGLE_TODO':
            return {
                ...state,
                items: state.items.map(item =>
                    item.id === action.payload
                        ? { ...item, completed: !item.completed }
                        : item
                )
            };
        case 'SET_FILTER':
            return { ...state, filter: action.payload };
        default:
            return state;
    }
}

function TodoApp() {
    const [state, dispatch] = useReducer(todoReducer, initialState);

    const addTodo = useCallback((text) => {
        dispatch({ type: 'ADD_TODO', payload: text });
    }, []);

    const filteredItems = state.items.filter(item => {
        if (state.filter === 'active') return !item.completed;
        if (state.filter === 'completed') return item.completed;
        return true;
    });

    return (
        <div>
            <h1>Todo App ⚛️</h1>
            {/* ... render UI */}
        </div>
    );
}