0
0
Lập trình
Hưng Nguyễn Xuân 1
Hưng Nguyễn Xuân 1xuanhungptithcm

`useEffect` – Hiểu rõ về hiệu ứng phụ trong React

Đăng vào 7 tháng trước

• 7 phút đọc

📋 Mục lục

  1. Giới thiệu về useEffect
  2. Cú pháp và mảng phụ thuộc
  3. Ví dụ thực tế: Lấy dữ liệu với trạng thái tải và lỗi
  4. Thực tiễn tốt nhất và mô hình phổ biến
  5. Lỗi thường gặp và cách khắc phục
  6. Thách thức thực hành
  7. Câu hỏi thường gặp

1. Giới thiệu về useEffect

useEffect là một Hook trong React cho phép bạn kết nối các thành phần của mình với các hệ thống bên ngoài. Nó cho phép bạn thực thi các "hiệu ứng phụ" sau khi thành phần đã được render. Hiệu ứng phụ là bất kỳ mã nào ảnh hưởng đến yếu tố bên ngoài thành phần đó, chẳng hạn như:

  • Gọi API để tìm dữ liệu.
  • Đăng ký sự kiện (ví dụ: window.addEventListener).
  • Manipulation DOM thủ công.
  • Thời gian như setInterval hoặc setTimeout.

useEffect thay thế cho các phương thức vòng đời componentDidMount, componentDidUpdatecomponentWillUnmount trong các thành phần lớp.

2. Cú pháp và mảng phụ thuộc

javascript Copy
useEffect(() => {
  // ...mã hiệu ứng của bạn...

  return () => {
    // ...hàm dọn dẹp (tùy chọn)...
  };
}, [mảng phụ thuộc]);

Phần quan trọng nhất của useEffectmảng phụ thuộc. Nó kiểm soát khi nào hiệu ứng sẽ được thực thi:

  1. [phụ thuộc1, phụ thuộc2]: Hiệu ứng sẽ được thực thi chỉ nếu bất kỳ phụ thuộc nào đã thay đổi từ render cuối cùng. Đây là trường hợp phổ biến nhất.
  2. [] (mảng rỗng): Hiệu ứng sẽ được thực thi một lần duy nhất, ngay sau khi render ban đầu (tương đương với componentDidMount). Hoàn hảo cho các khởi tạo hoặc lấy dữ liệu không thay đổi.
  3. Không cung cấp mảng: Hiệu ứng sẽ được thực thi sau mỗi lần render. Cẩn thận! Điều này có thể gây ra vòng lặp vô hạn nếu hiệu ứng cập nhật trạng thái.

Hàm dọn dẹp (return) là tùy chọn và sẽ được thực thi:

  • Trước khi hiệu ứng được thực thi lại (nếu một phụ thuộc đã thay đổi).
  • Khi thành phần được gỡ bỏ (biến mất khỏi màn hình).

Điều này rất quan trọng để dọn dẹp các đăng ký, bộ đếm thời gian hoặc lắng nghe và tránh rò rỉ bộ nhớ.


3. Ví dụ thực tế: Lấy dữ liệu với trạng thái tải và lỗi

Đây là trường hợp sử dụng phổ biến nhất cho useEffect. Chúng ta sẽ lấy dữ liệu của một Pokémon và quản lý các trạng thái trung gian.

javascript Copy
import React, { useState, useEffect } from 'react';

function PokemonInfo({ pokemonName }) {
  const [pokemon, setPokemon] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!pokemonName) return;

    // Reset trạng thái khi bắt đầu tìm kiếm mới
    setLoading(true);
    setError(null);
    setPokemon(null);

    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`)
      .then(response => {
        if (!response.ok) {
          throw new Error('Không tìm thấy Pokémon');
        }
        return response.json();
      })
      .then(data => {
        setPokemon(data);
      })
      .catch(error => {
        setError(error.message);
      })
      .finally(() => {
        setLoading(false);
      });

  }, [pokemonName]); // Hiệu ứng phụ thuộc vào tên Pokémon

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

  return (
    <div>
      {pokemon && (
        <>
          <h3>{pokemon.name}</h3>
          <img src={pokemon.sprites.front_default} alt={pokemon.name} />
        </>
      )}
    </div>
  );
}

4. ✅ Thực tiễn tốt nhất và mô hình phổ biến

  1. Một hiệu ứng, một trách nhiệm: Nếu bạn có các logic không liên quan, hãy tách chúng thành nhiều useEffect. Một cho việc lấy dữ liệu, một cho lắng nghe sự kiện, v.v. Điều này giúp mã sạch hơn.
  2. Luôn dọn dẹp hiệu ứng của bạn: Nếu bạn đăng ký một cái gì đó, tạo một bộ đếm thời gian hoặc một lắng nghe, luôn trả về một hàm dọn dẹp để hủy bỏ nó.
  3. Đừng quên các phụ thuộc: Linter của React thường sẽ cảnh báo bạn nếu bạn sử dụng một biến hoặc hàm bên trong hiệu ứng nhưng không đưa nó vào mảng. Đừng bỏ qua! Bỏ qua phụ thuộc có thể gây ra lỗi rất khó theo dõi.
  4. Hàm trong mảng phụ thuộc: Nếu một hàm được sử dụng trong useEffect, nó phải có trong mảng. Nhưng nếu hàm đó được định nghĩa lại trong mỗi lần render, sẽ khiến hiệu ứng luôn được thực thi. Giải pháp là định nghĩa hàm trong useEffect hoặc ghi nhớ nó với useCallback.

5. 🚨 Lỗi thường gặp và cách khắc phục

  • Lỗi: Vòng lặp vô hạn.

    javascript Copy
    // SAI ❌
    const [count, setCount] = useState(0);
    useEffect(() => {
      // Hiệu ứng này thay đổi trạng thái, gây ra re-render,
      // điều này lại chạy lại hiệu ứng, và cứ như vậy mãi.
      setCount(count + 1);
    }); // Không có mảng phụ thuộc
    • Giải pháp: Thêm mảng phụ thuộc. Nếu bạn muốn nó chỉ chạy một lần, hãy sử dụng []. Nếu bạn muốn nó phản ứng với sự thay đổi, hãy bao gồm phụ thuộc [biếnKhác].
  • Lỗi: Dữ liệu "cũ" (stale state) do thiếu phụ thuộc.

    javascript Copy
    // SAI ❌
    const [count, setCount] = useState(0);
    useEffect(() => {
      // Bộ đếm thời gian này luôn "thấy" count là 0, vì hàm
      // của bộ đếm thời gian được tạo ra trong render đầu tiên và không bao giờ được cập nhật.
      const intervalId = setInterval(() => {
        console.log(`Giá trị bộ đếm là: ${count}`);
      }, 1000);
      return () => clearInterval(intervalId);
    }, []); // Mảng rỗng nói với React: "không bao giờ chạy lại điều này"
    • Giải pháp 1: Thêm phụ thuộc.

      javascript Copy
      // ĐÚNG ✅ (nhưng tái tạo bộ đếm thời gian mỗi khi count thay đổi)
      useEffect(() => { ... }, [count]);
    • Giải pháp 2: Sử dụng cập nhật chức năng (tốt hơn trong trường hợp này).

      javascript Copy
      // TUYỆT VỜI ✅
      // Bên trong `setCount`, React luôn cho bạn giá trị mới nhất.
      // Chúng ta không cần `count` làm phụ thuộc.
      useEffect(() => {
        const intervalId = setInterval(() => {
           setCount(c => c + 1); // sử dụng phiên bản mới nhất của trạng thái
        }, 1000);
        return () => clearInterval(intervalId);
      }, []);

6. 🚀 Thách thức thực hành

  1. Bộ đếm thời gian với Start/Stop/Reset: Sử dụng useEffectuseState để tạo một đồng hồ bấm giờ chức năng.
  2. Theo dõi chuột: Tạo một thành phần hiển thị tọa độ X và Y của chuột theo thời gian thực. (Gợi ý: window.addEventListener('mousemove', ...), và đừng quên dọn dẹp lắng nghe).
  3. Tiêu đề trang động: Làm cho tiêu đề của tài liệu (document.title) được cập nhật để hiển thị giá trị của một bộ đếm hoặc một input.
  4. Tự động lưu: Tạo một textarea tự động lưu nội dung của nó vào localStorage sau 2 giây khi người dùng ngừng nhập.

7. Câu hỏi thường gặp

Câu hỏi 1: useEffect có thể sử dụng nhiều lần trong một thành phần không?
Có, bạn có thể sử dụng useEffect nhiều lần trong cùng một thành phần để tách biệt các logic khác nhau.

Câu hỏi 2: Khi nào nên sử dụng useEffect mà không có mảng phụ thuộc?
Bạn nên tránh việc này trừ khi bạn chắc chắn rằng bạn cần hiệu ứng chạy sau mỗi lần render, vì điều này có thể dẫn đến hiệu suất kém và lỗi khó theo dõi.

Câu hỏi 3: Có cách nào thay thế useEffect không?
useEffect là phương pháp chính để xử lý hiệu ứng phụ trong React. Tuy nhiên, bạn có thể sử dụng các thư viện bên ngoài như redux-saga hoặc react-query tùy thuộc vào nhu cầu của ứng dụng.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào