Trong thế giới phát triển web hiện đại, việc giao tiếp với server để lấy hoặc gửi dữ liệu là một phần không thể thiếu của bất kỳ ứng dụng nào. Đối với các nhà phát triển React, có nhiều cách để thực hiện điều này, nhưng Fetch API nổi lên như một công cụ mạnh mẽ, hiện đại và được tích hợp sẵn trong trình duyệt.
Bài viết này sẽ là một hướng dẫn toàn diện, đi sâu vào mọi khía cạnh của việc sử dụng Fetch API trong môi trường React. Dù bạn là người mới bắt đầu hay đã có kinh nghiệm, bạn cũng sẽ tìm thấy những kiến thức hữu ích để tối ưu hóa việc gọi API trong dự án của mình.
Fetch API là gì? Tại sao nên sử dụng nó trong React?
Fetch API là một phương thức dựa trên Promise, được tích hợp sẵn trong hầu hết các trình duyệt hiện đại (trừ IE), cung cấp một cách dễ dàng và logic để tìm nạp tài nguyên qua mạng. Nó là sự thay thế mạnh mẽ và linh hoạt hơn cho XMLHttpRequest
(XHR) đã cũ.
Vậy tại sao Fetch lại được ưa chuộng trong React?
- Tích hợp sẵn (Native): Bạn không cần cài đặt thêm bất kỳ thư viện bên ngoài nào như Axios hay jQuery. Điều này giúp giữ cho gói ứng dụng (bundle) của bạn nhẹ hơn.
- Cú pháp hiện đại: Sử dụng Promises, cho phép bạn viết mã bất đồng bộ một cách sạch sẽ và dễ đọc hơn, đặc biệt khi kết hợp với cú pháp
async/await
. - Linh hoạt: Cung cấp một bộ tùy chọn mạnh mẽ (thông qua đối tượng
options
) để xử lý các yêu cầu HTTP phức tạp, bao gồm các phương thức khác nhau (POST, PUT, DELETE), headers, CORS, và quản lý cache. - Tương thích tốt với Hooks: Hoạt động trơn tru với các React Hooks như
useState
vàuseEffect
để quản lý trạng thái dữ liệu, trạng thái tải và lỗi một cách hiệu quả.
Các thao tác cơ bản với Fetch API trong React
Hãy bắt đầu với những viên gạch đầu tiên. Chúng ta sẽ sử dụng các hooks useState
và useEffect
để thực hiện một yêu cầu GET
đơn giản nhằm lấy dữ liệu và hiển thị lên giao diện.
1. Thực hiện yêu cầu GET
Đây là trường hợp phổ biến nhất: lấy dữ liệu từ một API và hiển thị nó.
Kịch bản: Chúng ta sẽ lấy danh sách người dùng từ API công khai JSONPlaceholder.
import React, { useState, useEffect } from 'react'
function UserList() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
// Định nghĩa hàm fetch để có thể sử dụng async/await
const fetchUsers = async () => {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users',
)
// Fetch không tự động throw lỗi cho các mã trạng thái HTTP xấu (như 404, 500)
// Vì vậy, chúng ta cần kiểm tra thủ công
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json() // Chuyển đổi response thành JSON
setUsers(data)
} catch (e) {
setError(e.message)
} finally {
setLoading(false) // Dừng hiển thị trạng thái loading dù thành công hay thất bại
}
}
fetchUsers()
}, []) // Mảng phụ thuộc rỗng đảm bảo useEffect chỉ chạy một lần sau khi component được render
if (loading) return <div>Đang tải dữ liệu...</div>
if (error) return <div>Đã xảy ra lỗi: {error}</div>
return (
<div>
<h1>Danh sách Người dùng</h1>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
)
}
export default UserList
Phân tích mã nguồn:
useState
: Chúng ta sử dụng ba trạng thái.users
để lưu trữ dữ liệu,loading
để theo dõi quá trình tải, vàerror
để bắt bất kỳ lỗi nào.useEffect
: Đây là nơi logic gọi API được đặt. Việc đặt nó tronguseEffect
đảm bảo rằng việc tìm nạp dữ liệu không chặn quá trình render giao diện. Mảng phụ thuộc rỗng[]
có nghĩa là hiệu ứng này chỉ chạy một lần, tương tự nhưcomponentDidMount
trong class component.async/await
: Cú pháp này giúp mã bất đồng bộ trở nên dễ đọc hơn nhiều so với việc sử dụng chuỗi.then()
.- Xử lý lỗi: Một điểm quan trọng cần nhớ là Fetch chỉ từ chối một promise (reject) khi có lỗi mạng. Với các phản hồi HTTP lỗi như 404 hoặc 500, nó vẫn sẽ hoàn thành (resolve). Do đó, chúng ta phải kiểm tra thuộc tính
response.ok
(hoặcresponse.status
) để xử lý lỗi một cách chính xác. response.json()
: Phương thức này cũng là bất đồng bộ và trả về một Promise phân giải với kết quả là dữ liệu JSON.
2. Gửi dữ liệu với yêu cầu POST
Bây giờ, hãy xem cách gửi dữ liệu lên server, ví dụ như tạo một bài đăng mới.
import React, { useState } from 'react'
function CreatePost() {
const [title, setTitle] = useState('')
const [body, setBody] = useState('')
const [message, setMessage] = useState('')
const handleSubmit = async (e) => {
e.preventDefault() // Ngăn form gửi theo cách truyền thống
setMessage('Đang gửi...')
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts',
{
method: 'POST', // Chỉ định phương thức
body: JSON.stringify({
// Chuyển đổi đối tượng JavaScript thành chuỗi JSON
title: title,
body: body,
userId: 1, // Thường thì userId sẽ được lấy từ thông tin đăng nhập
}),
headers: {
'Content-type': 'application/json; charset=UTF-8', // Chỉ định kiểu nội dung
},
},
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
setMessage(`Bài viết đã được tạo thành công với ID: ${data.id}`)
setTitle('')
setBody('')
} catch (error) {
setMessage(`Lỗi: ${error.message}`)
}
}
return (
<form onSubmit={handleSubmit}>
<h2>Tạo Bài viết Mới</h2>
<div>
<label>Tiêu đề:</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div>
<label>Nội dung:</label>
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
required
/>
</div>
<button type="submit">Tạo</button>
{message && <p>{message}</p>}
</form>
)
}
Những điểm chính trong yêu cầu POST:
fetch
nhận một đối số thứ hai là một đối tượngoptions
.method: 'POST'
: Chúng ta phải chỉ định rõ phương thức HTTP.body
: Đây là nơi chứa dữ liệu bạn muốn gửi đi. Dữ liệu cần được chuyển đổi sang định dạng chuỗi, thường là JSON bằng cách sử dụngJSON.stringify()
.headers
: Rất quan trọng! Header'Content-Type': 'application/json'
báo cho server biết rằng chúng ta đang gửi dữ liệu dưới dạng JSON.
Nâng cao: Tái sử dụng Logic với Custom Hook
Khi ứng dụng của bạn lớn lên, bạn sẽ thấy mình lặp đi lặp lại logic useState
và useEffect
để gọi API ở nhiều component khác nhau. Đây là lúc Custom Hook tỏa sáng! Chúng ta có thể đóng gói logic này vào một hook có thể tái sử dụng.
Hãy tạo một hook useFetch
đa năng.
// hooks/useFetch.js
import { useState, useEffect } from 'react'
function useFetch(url, options = {}) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
// Sử dụng AbortController để hủy yêu cầu khi component bị unmount
const controller = new AbortController()
const signal = controller.signal
const fetchData = async () => {
setLoading(true)
setError(null)
try {
const response = await fetch(url, { ...options, signal })
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
} catch (e) {
if (e.name !== 'AbortError') {
setError(e.message)
}
} finally {
// Chỉ set loading thành false nếu không phải là lỗi hủy bỏ
if (signal.aborted === false) {
setLoading(false)
}
}
}
fetchData()
// Hàm dọn dẹp (cleanup function)
return () => {
controller.abort() // Hủy yêu cầu fetch khi component unmount
}
}, [url]) // Chạy lại hiệu ứng nếu URL thay đổi
return { data, loading, error }
}
export default useFetch
Cách sử dụng Custom Hook useFetch
:
Bây giờ, component UserList
của chúng ta trở nên gọn gàng hơn rất nhiều:
import React from 'react'
import useFetch from './hooks/useFetch' // Import custom hook
function UserList() {
const {
data: users,
loading,
error,
} = useFetch('https://jsonplaceholder.typicode.com/users')
if (loading) return <div>Đang tải dữ liệu...</div>
if (error) return <div>Đã xảy ra lỗi: {error}</div>
return (
<div>
<h1>Danh sách Người dùng</h1>
<ul>
{users?.map(
(
user, // Sử dụng optional chaining `?.` để phòng trường hợp data là null
) => (
<li key={user.id}>{user.name}</li>
),
)}
</ul>
</div>
)
}
Tại sao Custom Hook này lại tuyệt vời?
- Tái sử dụng (Reusable): Bạn có thể dùng
useFetch
ở bất cứ đâu trong ứng dụng của mình. - Sạch sẽ (Clean): Logic phức tạp được trừu tượng hóa, giúp component của bạn chỉ tập trung vào việc hiển thị giao diện.
- Xử lý hủy bỏ (Abort Handling): Đây là một cải tiến quan trọng.
AbortController
ngăn chặn lỗi "memory leak" xảy ra khi một component bị hủy (unmount) trước khi yêu cầu API hoàn thành. Hàm dọn dẹp tronguseEffect
sẽ gọicontroller.abort()
, hủy yêu cầu đang chờ xử lý.
So sánh Fetch API và Axios
Axios là một thư viện phổ biến khác để thực hiện các yêu cầu HTTP. Vậy khi nào nên chọn Fetch, khi nào nên chọn Axios?
Tính năng | Fetch API | Axios |
---|---|---|
Cài đặt | Tích hợp sẵn | Cần cài đặt từ npm/yarn |
Xử lý lỗi HTTP | Không tự động từ chối promise | Tự động từ chối promise khi có lỗi (4xx, 5xx) |
Biến đổi dữ liệu | Cần .json() hoặc .text() thủ công | Tự động chuyển đổi từ/sang JSON |
Hủy yêu cầu | Hỗ trợ qua AbortController (hơi dài dòng) | Hỗ trợ qua CancelToken hoặc AbortController |
Tiến trình tải lên/tải xuống | Không hỗ trợ trực tiếp | Có hỗ trợ |
Tương thích trình duyệt cũ | Cần polyfill cho IE | Hoạt động trên hầu hết các trình duyệt |
Kích thước | 0KB (tích hợp sẵn) | ~12KB (nhỏ, nhưng vẫn là một phụ thuộc) |
Từ bảng so sánh trên, chúng ta có:
- Lựa chọn Fetch API khi: Bạn muốn một giải pháp không cần phụ thuộc, nhẹ nhàng, và bạn không ngại viết thêm một chút mã để xử lý lỗi và biến đổi dữ liệu. Rất phù hợp cho các dự án nhỏ hoặc khi bạn muốn kiểm soát hoàn toàn quá trình.
- Lựa chọn Axios khi: Bạn cần các tính năng nâng cao như tự động chuyển đổi JSON, xử lý lỗi HTTP đơn giản hơn, interceptors (chặn và thay đổi yêu cầu/phản hồi), và hỗ trợ tiến trình tải lên. Rất tốt cho các ứng dụng lớn và phức tạp.
Kết luận: Fetch API là một công cụ mạnh mẽ
Fetch API là một công cụ mạnh mẽ và thiết yếu trong "kho đồ nghề" của một nhà phát triển React. Bằng cách hiểu rõ cách hoạt động của nó, kết hợp nhuần nhuyễn với các React Hooks như useState
, useEffect
, và đóng gói logic bằng các Custom Hook, bạn có thể xây dựng các ứng dụng React hiệu quả, sạch sẽ và có khả năng bảo trì cao.
Hy vọng rằng bài viết toàn diện này đã cung cấp cho bạn cái nhìn sâu sắc và những kiến thức cần thiết để tự tin làm chủ việc gọi API bằng Fetch trong các dự án React của mình.
Chúc bạn viết code vui vẻ cùng với React!