React Roadmap: Cách Gọi API Với Axios và Fetch

Chào mừng các bạn quay trở lại với chuỗi bài viết về Lộ trình học React 2025! Sau khi chúng ta đã tìm hiểu về các khái niệm cơ bản như React là gì, JSX, Props và State, Component (Class và Functional), và cách quản lý dữ liệu với useState và useEffect, đã đến lúc chúng ta kết nối ứng dụng frontend của mình với thế giới bên ngoài: các API. Đây là một bước cực kỳ quan trọng, vì hầu hết các ứng dụng web hiện đại đều cần lấy hoặc gửi dữ liệu đến máy chủ.

Trong bài viết này, chúng ta sẽ đi sâu vào hai phương pháp phổ biến nhất để thực hiện các cuộc gọi API trong JavaScript nói chung và React nói riêng: sử dụng Fetch API (API tích hợp sẵn trong trình duyệt) và sử dụng thư viện Axios. Chúng ta sẽ tìm hiểu cách sử dụng cả hai, ưu nhược điểm của mỗi phương pháp, và cách tích hợp chúng một cách hiệu quả vào ứng dụng React của bạn.

Tại Sao Cần Gọi API Trong Ứng Dụng React?

Hãy tưởng tượng bạn đang xây dựng một ứng dụng thương mại điện tử. Dữ liệu về sản phẩm, thông tin người dùng, đơn hàng… thường không được lưu trữ trực tiếp trong mã nguồn frontend. Thay vào đó, chúng được lưu trữ trên máy chủ backend (cơ sở dữ liệu). Để hiển thị danh sách sản phẩm cho người dùng, thêm sản phẩm vào giỏ hàng, hoặc xử lý thanh toán, ứng dụng React của bạn cần giao tiếp với máy chủ backend thông qua các API (Application Programming Interfaces).

API về cơ bản là một “người trung gian” cho phép các hệ thống phần mềm khác nhau giao tiếp với nhau. Khi ứng dụng React của bạn cần dữ liệu, nó sẽ gửi một “yêu cầu” (request) đến một địa chỉ API cụ thể trên máy chủ. Máy chủ xử lý yêu cầu đó, lấy dữ liệu cần thiết (ví dụ: từ database), và gửi trả lại một “phản hồi” (response) chứa dữ liệu mà ứng dụng của bạn có thể sử dụng để hiển thị cho người dùng.

Các cuộc gọi API trong ứng dụng web diễn ra bất đồng bộ (asynchronous). Điều này có nghĩa là khi bạn gửi yêu cầu API, ứng dụng không bị “đóng băng” chờ đợi kết quả. Thay vào đó, nó tiếp tục thực thi các tác vụ khác, và khi phản hồi từ máy chủ đến, một hành động nào đó sẽ được kích hoạt (ví dụ: cập nhật giao diện người dùng với dữ liệu mới). Việc hiểu và xử lý các thao tác bất đồng bộ này là chìa khóa khi làm việc với API.

Fetch API: Cách Tiếp Cận Gốc của Trình Duyệt

Fetch API là một API hiện đại, tích hợp sẵn trong hầu hết các trình duyệt web hiện đại (và cả môi trường Node.js thông qua các thư viện shim), cung cấp một cách mạnh mẽ và linh hoạt để thực hiện các yêu cầu mạng. Nó dựa trên các Promise, giúp việc làm việc với các thao tác bất đồng bộ trở nên dễ dàng hơn so với phương pháp XMLHttpRequest cũ.

Thực Hiện Yêu Cầu GET Đơn Giản

Để lấy dữ liệu từ một API, bạn chỉ cần gọi hàm fetch() với URL của endpoint API:

fetch('https://api.example.com/data')
  .then(response => {
    // Kiểm tra xem phản hồi có thành công không (status code trong khoảng 200-299)
    if (!response.ok) {
      throw new Error(`Lỗi HTTP! Trạng thái: ${response.status}`);
    }
    // Chuyển đổi phản hồi sang JSON
    return response.json();
  })
  .then(data => {
    // Sử dụng dữ liệu nhận được
    console.log('Dữ liệu:', data);
  })
  .catch(error => {
    // Xử lý lỗi trong quá trình fetch hoặc xử lý response
    console.error('Có lỗi xảy ra khi gọi API:', error);
  });

Giải thích:

  • fetch(url): Bắt đầu một yêu cầu mạng và trả về một Promise.
  • .then(response => ...): Khi Promise đầu tiên được giải quyết thành công (nhận được phản hồi từ máy chủ – không phải là yêu cầu thành công về mặt HTTP status code), callback này được thực thi với đối tượng response.
  • if (!response.ok) { ... }: Quan trọng! Fetch API coi một yêu cầu thành công nếu máy chủ phản hồi, ngay cả khi đó là lỗi 404 hay 500. Bạn cần kiểm tra thuộc tính response.ok (true nếu status code là 2xx) để xác định xem yêu cầu có thực sự thành công về mặt nghiệp vụ hay không. Nếu không, chúng ta ném ra lỗi để block .catch() bắt lấy.
  • response.json(): Đây cũng là một thao tác bất đồng bộ! Nó đọc luồng phản hồi và chuyển đổi nó thành một đối tượng JSON. Nó cũng trả về một Promise.
  • .then(data => ...): Khi Promise từ response.json() được giải quyết, bạn nhận được dữ liệu JSON đã được phân tích cú pháp.
  • .catch(error => ...): Bắt bất kỳ lỗi nào xảy ra trong chuỗi Promise, bao gồm lỗi mạng (ví dụ: không kết nối được) hoặc lỗi bạn chủ động throw ra (như khi response.ok là false).

Thực Hiện Yêu Cầu POST (và các phương thức khác)

Đối với các yêu cầu phức tạp hơn như POST, PUT, DELETE, bạn cần cung cấp đối tượng tùy chọn thứ hai cho hàm fetch():

const postData = {
  title: 'Bài viết mới',
  body: 'Nội dung của bài viết...',
  userId: 1,
};

fetch('https://api.example.com/posts', {
  method: 'POST', // Hoặc 'PUT', 'DELETE', v.v.
  headers: {
    'Content-Type': 'application/json',
    // Các headers khác như Authorization
  },
  body: JSON.stringify(postData), // Dữ liệu gửi đi (chuyển sang chuỗi JSON)
})
  .then(response => {
    if (!response.ok) {
      throw new Error(`Lỗi HTTP! Trạng thái: ${response.status}`);
    }
    return response.json(); // Hoặc response.text(), v.v. tùy loại phản hồi
  })
  .then(data => {
    console.log('Bài viết mới được tạo:', data);
  })
  .catch(error => {
    console.error('Có lỗi khi tạo bài viết:', error);
  });

Các tùy chọn phổ biến:

  • method: Phương thức HTTP (‘GET’, ‘POST’, ‘PUT’, ‘DELETE’, v.v.). Mặc định là ‘GET’.
  • headers: Một đối tượng chứa các HTTP headers (ví dụ: 'Content-Type' để cho máy chủ biết định dạng dữ liệu bạn gửi, 'Authorization' cho token xác thực).
  • body: Dữ liệu bạn muốn gửi đi trong yêu cầu (phù hợp với phương thức POST, PUT). Đối với JSON, bạn cần chuyển đổi đối tượng JavaScript sang chuỗi bằng JSON.stringify().
  • mode, cache, credentials, v.v.: Các tùy chọn nâng cao khác.

Ưu và Nhược Điểm của Fetch API

Ưu điểm:

  • Native: Không cần cài đặt thư viện bên ngoài. Code của bạn sẽ nhẹ hơn.
  • Dựa trên Promise: Giúp xử lý bất đồng bộ dễ dàng và cấu trúc code rõ ràng hơn.
  • Mạnh mẽ: Cung cấp đủ chức năng cho các yêu cầu cơ bản và nâng cao.

Nhược điểm:

  • Xử lý lỗi thủ công: Phải tự kiểm tra response.ok để xác định yêu cầu thành công hay thất bại về mặt HTTP.
  • Không có tính năng nâng cao tích hợp sẵn: Thiếu các tính năng như interceptors (chặn và chỉnh sửa yêu cầu/phản hồi), hủy yêu cầu (cần sử dụng AbortController), theo dõi tiến độ upload/download.
  • Xử lý dữ liệu: Phải gọi thêm .json() (hoặc .text(), v.v.) sau khi nhận được phản hồi.
  • Không hỗ trợ cũ hơn: Cần polyfill cho các trình duyệt rất cũ (mặc dù giờ không còn quá phổ biến).

Axios: Thư Viện HTTP Client Phổ Biến

Axios là một thư viện JavaScript mã nguồn mở, dựa trên Promise, dùng để thực hiện các yêu cầu HTTP từ cả trình duyệt và Node.js. Nó đã trở thành một trong những lựa chọn phổ biến nhất cho việc gọi API trong các ứng dụng frontend hiện đại, bao gồm cả React, nhờ vào bộ tính năng phong phú và trải nghiệm phát triển (DX) tốt hơn.

Cài Đặt Axios

Axios không tích hợp sẵn, nên bạn cần cài đặt nó vào dự án của mình:

npm install axios

hoặc

yarn add axios

Sau đó, bạn chỉ cần import nó vào file cần sử dụng:

import axios from 'axios';

Thực Hiện Yêu Cầu GET Đơn Giản với Axios

Axios cung cấp các phương thức trực quan cho từng loại yêu cầu HTTP:

import axios from 'axios';

axios.get('https://api.example.com/data')
  .then(response => {
    // Dữ liệu đã nằm trong response.data và đã được tự động phân tích cú pháp JSON
    console.log('Dữ liệu:', response.data);
  })
  .catch(error => {
    // Axios xử lý các lỗi HTTP (status code ngoài 2xx) là lỗi
    console.error('Có lỗi xảy ra khi gọi API:', error);

    if (error.response) {
      // Request được gửi và server trả về status code không phải 2xx
      console.error('Dữ liệu lỗi:', error.response.data);
      console.error('Trạng thái lỗi:', error.response.status);
      console.error('Headers lỗi:', error.response.headers);
    } else if (error.request) {
      // Request được gửi nhưng không nhận được phản hồi (ví dụ: server down)
      console.error('Request:', error.request);
    } else {
      // Có gì đó xảy ra trong quá trình thiết lập request gây ra lỗi
      console.error('Error message:', error.message);
    }
  });

Điểm khác biệt chính so với Fetch:

  • Axios tự động xử lý phản hồi JSON. Dữ liệu bạn cần đã có sẵn trong response.data.
  • Axios coi bất kỳ status code nào ngoài 2xx là lỗi và tự động chuyển nó vào block .catch(). Đối tượng lỗi của Axios cũng cung cấp nhiều thông tin chi tiết hơn (error.response, error.request, error.message).

Thực Hiện Yêu Cầu POST với Axios

import axios from 'axios';

const postData = {
  title: 'Bài viết mới',
  body: 'Nội dung của bài viết...',
  userId: 1,
};

axios.post('https://api.example.com/posts', postData) // Dữ liệu được truyền trực tiếp
  .then(response => {
    console.log('Bài viết mới được tạo:', response.data);
  })
  .catch(error => {
    console.error('Có lỗi khi tạo bài viết:', error);
    // Xử lý lỗi chi tiết như ví dụ GET
  });

Quan sát:

  • Bạn truyền dữ liệu POST trực tiếp dưới dạng đối tượng JavaScript (postData). Axios sẽ tự động chuyển đổi nó sang chuỗi JSON và đặt header Content-Type: application/json.
  • Axios cung cấp các phương thức tiện lợi như axios.get(), axios.post(), axios.put(), axios.delete(), v.v.

Các Tính Năng Nâng Cao của Axios

Axios nổi bật với một số tính năng mạnh mẽ:

  • Interceptors (Bộ chặn): Cho phép bạn chặn các yêu cầu trước khi chúng được gửi đi hoặc chặn các phản hồi trước khi chúng được xử lý bởi .then() hoặc .catch(). Điều này rất hữu ích cho việc thêm header xác thực vào mọi yêu cầu, xử lý lỗi tập trung, ghi log, v.v.
  • Tự động chuyển đổi JSON: Tự động chuyển đổi dữ liệu yêu cầu sang JSON và phân tích cú pháp phản hồi JSON.
  • Xử lý lỗi chi tiết: Cung cấp đối tượng lỗi giàu thông tin.
  • Hủy yêu cầu: Cho phép bạn dễ dàng hủy các yêu cầu đang thực hiện (rất hữu ích khi component unmount hoặc người dùng thực hiện tìm kiếm mới trước khi kết quả cũ về).
  • Theo dõi tiến độ: Hỗ trợ theo dõi tiến độ upload/download.
  • Instances tùy chỉnh: Cho phép tạo các instance Axios với cấu hình mặc định riêng (ví dụ: base URL, headers), giúp quản lý nhiều endpoint API dễ dàng hơn.

So Sánh Fetch và Axios

Đây là bảng so sánh nhanh giữa hai phương pháp:

Tính năng Fetch API Axios
Kiểu API tích hợp sẵn trong trình duyệt Thư viện JavaScript bên ngoài
Cài đặt Không cần (native) Cần cài đặt (npm install axios)
Xử lý lỗi HTTP (status codes 4xx, 5xx) Không tự động ném lỗi, phải kiểm tra response.ok thủ công Tự động ném lỗi, vào block .catch()
Dữ liệu JSON Phải gọi response.json() thủ công Tự động chuyển đổi (dữ liệu trong response.data)
Interceptors Không có tích hợp sẵn Hỗ trợ mạnh mẽ
Hủy yêu cầu Cần sử dụng AbortController riêng API hủy yêu cầu tích hợp sẵn dễ dùng hơn
Theo dõi tiến độ Hỗ trợ hạn chế Hỗ trợ tốt cho upload/download
Tương thích trình duyệt cũ Cần polyfill Hỗ trợ tốt hơn cho các trình duyệt cũ (nhờ sử dụng XMLHttpRequest bên dưới)
Kích thước bundle Không thêm kích thước Thêm kích thước nhất định (khoảng vài KB)

Khi nào nên dùng Fetch?

  • Khi bạn cần thực hiện các yêu cầu đơn giản và không muốn thêm một thư viện vào dự án của mình để giữ cho bundle size nhỏ nhất.
  • Khi bạn chủ yếu làm việc với các API REST cơ bản.

Khi nào nên dùng Axios?

  • Khi bạn cần các tính năng mạnh mẽ như interceptors, hủy yêu cầu dễ dàng, theo dõi tiến độ.
  • Khi bạn muốn xử lý lỗi nhất quán và nhận thông tin lỗi chi tiết.
  • Khi bạn làm việc với các API phức tạp hơn hoặc cần cấu hình nhiều yêu cầu.
  • Khi bạn muốn một trải nghiệm phát triển (DX) mượt mà hơn.

Trong thực tế, Axios thường được ưa chuộng hơn trong các dự án React lớn và phức tạp nhờ vào các tính năng giúp quản lý API call hiệu quả hơn.

Tích Hợp API Calls vào React Components

Trong React, việc gọi API thường diễn ra trong các lifecycle methods của Class Component hoặc phổ biến hơn hiện nay là trong Hook useEffect của Functional Component. Mục tiêu là fetch dữ liệu khi component được mount (xuất hiện trên UI) hoặc khi một dependency nào đó thay đổi, sau đó lưu dữ liệu vào state để component render lại với dữ liệu mới.

Hãy xem một ví dụ sử dụng useEffect để fetch dữ liệu khi component được mount:

import React, { useState, useEffect } from 'react';
import axios from 'axios'; // Hoặc không cần import nếu dùng Fetch

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Hàm bất đồng bộ để fetch data
    const fetchData = async () => {
      try {
        // Sử dụng Axios:
        const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
        setData(response.data);

        // Hoặc sử dụng Fetch:
        // const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        // if (!response.ok) {
        //   throw new Error(`HTTP error! status: ${response.status}`);
        // }
        // const jsonData = await response.json();
        // setData(jsonData);

      } catch (error) {
        setError(error);
        console.error("Có lỗi khi fetch dữ liệu:", error);
      } finally {
        setLoading(false); // Dù thành công hay thất bại, đều kết thúc trạng thái loading
      }
    };

    fetchData();

    // Cleanup function: Quan trọng để hủy request nếu component unmount
    // Điều này tránh cập nhật state trên component đã unmount, gây lỗi memory leak
    return () => {
      // Đối với Axios, bạn có thể sử dụng AbortController:
      // const controller = new AbortController();
      // axios.get(..., { signal: controller.signal });
      // return () => controller.abort();

      // Đối với Fetch, bạn cũng sử dụng AbortController tương tự
    };

  }, []); // Mảng dependency rỗng [] nghĩa là hiệu ứng chỉ chạy 1 lần khi component mount

  if (loading) {
    return <div>Đang tải dữ liệu...</div>;
  }

  if (error) {
    return <div>Lỗi: {error.message}</div>;
  }

  return (
    <div>
      <h3>Dữ liệu đã tải:</h3>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataFetcher;

Lưu ý quan trọng:

  • Chúng ta sử dụng useState để quản lý 3 trạng thái quan trọng: dữ liệu (data), trạng thái tải (loading), và lỗi (error). Việc hiển thị UI dựa vào các trạng thái này giúp người dùng biết điều gì đang xảy ra. (Xem thêm về State trong React).
  • useEffect được dùng để thực hiện side effect (gọi API) sau khi component render lần đầu. Mảng dependency rỗng [] đảm bảo nó chỉ chạy một lần khi mount. (Xem thêm về useEffect).
  • Bên trong useEffect, chúng ta khai báo một hàm bất đồng bộ fetchData và gọi nó ngay lập tức. Cú pháp async/await giúp làm việc với Promise dễ đọc hơn.
  • Khối try...catch...finally là cách chuẩn để xử lý các thao tác có thể gây lỗi (như gọi API).
  • Cleanup function (hàm trả về từ useEffect) rất quan trọng. Nó chạy khi component unmount. Đối với API call, bạn nên hủy yêu cầu nếu nó vẫn đang chạy khi component không còn tồn tại. Điều này tránh các lỗi tiềm ẩn và cải thiện hiệu suất (mặc dù ví dụ trên chưa cài đặt hủy yêu cầu, bạn nên tìm hiểu về AbortController cho Fetch/Axios nếu làm việc với các request dài hoặc component thường xuyên mount/unmount).

Tái Sử Dụng Logic API Call

Nếu bạn có nhiều component cần gọi cùng một hoặc các loại API tương tự, việc lặp lại logic fetch data, loading state, và error handling trong mỗi useEffect sẽ trở nên cồng kềnh. Đây là lúc Custom Hooks phát huy tác dụng.

Bạn có thể tạo một custom hook useFetchData hoặc useApi để đóng gói toàn bộ logic fetch, quản lý state loading/error, và trả về dữ liệu. Điều này giúp component của bạn gọn gàng hơn và logic API call có thể được tái sử dụng dễ dàng trên toàn ứng dụng.

Kết Luận

Việc gọi API là một kỹ năng cốt lõi khi xây dựng các ứng dụng React tương tác với backend. Bạn có hai lựa chọn chính: sử dụng Fetch API tích hợp sẵn hoặc thư viện Axios phổ biến. Fetch API đủ tốt cho các nhu cầu cơ bản và giúp giữ bundle size nhỏ. Axios cung cấp bộ tính năng mạnh mẽ hơn, đặc biệt là interceptors và xử lý lỗi chi tiết, làm cho nó trở thành lựa chọn ưu tiên cho các dự án phức tạp hơn.

Quan trọng nhất là hiểu cách làm việc với các thao tác bất đồng bộ (Promise, async/await) và cách tích hợp chúng vào vòng đời của component React (thường là với useEffect), cùng với việc quản lý các trạng thái loading và error một cách hiệu quả.

Hãy thử cả hai phương pháp trong các dự án nhỏ để cảm nhận sự khác biệt và quyết định công cụ phù hợp nhất cho nhu cầu của bạn. Việc làm chủ các công cụ gọi API này sẽ mở ra khả năng kết nối ứng dụng React của bạn với thế giới dữ liệu rộng lớn, đưa ứng dụng của bạn lên một tầm cao mới!

Trong các bài viết tiếp theo của Lộ trình học React 2025, chúng ta sẽ tiếp tục khám phá các chủ đề quan trọng khác trên con đường trở thành nhà phát triển React chuyên nghiệp. Hẹn gặp lại các bạn!

Chỉ mục