Làm thế nào để tối ưu hóa API Calls trong Ứng dụng React lên đến 40%
Là lập trình viên React, chúng ta thường gặp phải những trường hợp cần đồng bộ hóa nhiều thay đổi trạng thái nhanh chóng với API. Việc thực hiện gọi API cho mỗi thay đổi nhỏ có thể không hiệu quả và gây quá tải cho cả client và server. Đây là lúc kỹ thuật debouncing và quản lý trạng thái thông minh phát huy tác dụng. Trong bài viết này, chúng ta sẽ xây dựng một hook React tùy chỉnh để nắm bắt các lệnh gọi cập nhật API song song bằng cách hợp nhất payloads và debounce lệnh gọi API.
Vấn đề đang gặp phải
Hãy tưởng tượng một trường nhập liệu, nơi người dùng có thể điều chỉnh cài đặt hoặc tùy chọn. Mỗi lần nhấn phím hoặc điều chỉnh có thể kích hoạt một lệnh gọi API để lưu trạng thái mới. Nếu người dùng thực hiện một số thay đổi liên tiếp nhanh chóng, điều này có thể dẫn đến một loạt yêu cầu API:
- Sử dụng tài nguyên mạng không hiệu quả.
- Tiềm ẩn race conditions.
- Tải không cần thiết trên máy chủ.
Giải pháp: Debouncing và useRef
1. Debouncing
Debouncing là một kỹ thuật được sử dụng để giới hạn tốc độ kích hoạt của một hàm. Thay vì gọi hàm ngay lập tức, bạn đợi một khoảng thời gian không hoạt động nhất định trước khi thực thi nó. Nếu một lệnh gọi khác đến trước khi hết thời gian chờ, bộ hẹn giờ sẽ được đặt lại.
Tại sao nên sử dụng Debouncing?
- Cải thiện hiệu suất: Giảm số lượng lệnh gọi API không cần thiết.
- Tối ưu hóa tài nguyên: Giảm thiểu tải máy chủ và mức sử dụng mạng.
- Nâng cao trải nghiệm người dùng: Ngăn chặn tình trạng lag và lỗi tiềm ẩn do các lệnh gọi liên tiếp, nhanh chóng.
2. Sử dụng useRef
Trong React, useRef là một hook cho phép bạn duy trì các giá trị có thể thay đổi giữa các lần render mà không kích hoạt re-render. Về cơ bản, nó là một container chứa một giá trị có thể thay đổi.
Tại sao nên sử dụng useRef ở đây?
- Duy trì các bản cập nhật tích lũy: Chúng ta cần theo dõi các bản cập nhật tích lũy giữa các lần render mà không gây ra re-render.
- Truy cập giá trị hiện tại có thể thay đổi: useRef cung cấp cho chúng ta một thuộc tính .current mà chúng ta có thể đọc và ghi.
Xây dựng Hook useDebouncedUpdate
Hãy đi sâu vào mã và tìm hiểu cách tất cả kết hợp với nhau thông qua một ví dụ dưới đây:
import { debounce } from "@mui/material";
import { useCallback, useEffect, useRef } from "react";
type DebouncedUpdateParams = {
id: string;
params: Record<string, any>;
};
function useDebouncedUpdate( apiUpdate: (params: DebouncedUpdateParams) => void,
delay: number = 300, ) {
const accumulatedUpdates = useRef<DebouncedUpdateParams | null>(null);
const processUpdates = useRef(
debounce(() => {
if (accumulatedUpdates.current) {
apiUpdate(accumulatedUpdates.current);
accumulatedUpdates.current = null;
}
}, delay),
).current;
const handleUpdate = useCallback(
(params: DebouncedUpdateParams) => {
accumulatedUpdates.current = {
id: params.id,
params: {
...(accumulatedUpdates.current?.params || {}),
...params.params,
},
};
processUpdates();
},
[processUpdates],
);
useEffect(() => {
return () => {
processUpdates.clear();
};
}, [processUpdates]);
return handleUpdate;
}
export default useDebouncedUpdate;
Chúng ta hãy cùng phân tích ví dụ trên.
1. Tích lũy bản cập nhật với useRef
Chúng ta khởi tạo một useRef được gọi là accumulatedUpdates để lưu trữ các tham số được kết hợp của tất cả các bản cập nhật đến.
const accumulatedUpdates = useRef<DebouncedUpdateParams | null>(null);
2. Debounce lệnh gọi API
Chúng ta tạo một hàm debounced processUpdates bằng cách sử dụng tiện ích debounce từ Material UI.
const processUpdates = useRef(
debounce(() => {
if (accumulatedUpdates.current) {
apiUpdate(accumulatedUpdates.current);
accumulatedUpdates.current = null;
}
}, delay),
).current;
Tại sao lại dùng useRef cho processUpdates? Chúng ta sử dụng useRef để đảm bảo rằng hàm debounced không được tạo lại trên mỗi lần render, điều này sẽ đặt lại bộ hẹn giờ debounce.
3. Xử lý bản cập nhật với useCallback
Hàm handleUpdate chịu trách nhiệm tích lũy các bản cập nhật và kích hoạt lệnh gọi API debounced.
const handleUpdate = useCallback(
(params: DebouncedUpdateParams) => {
accumulatedUpdates.current = {
id: params.id,
params: {
...(accumulatedUpdates.current?.params || {}),
...params.params,
},
};
processUpdates();
},
[processUpdates],
);
- Hợp nhất Params: Chúng ta hợp nhất các tham số mới với bất kỳ tham số hiện có nào để đảm bảo tất cả các bản cập nhật được ghi lại.
- Kích hoạt Debounce: Mỗi khi handleUpdate được gọi, chúng ta kích hoạt processUpdates(), nhưng lệnh gọi API thực tế bị debounced.
4. Dọn dẹp với useEffect
Chúng ta xóa hàm debounced khi component unmount để ngăn chặn rò rỉ bộ nhớ.
useEffect(() => {
return () => {
processUpdates.clear();
};
}, [processUpdates]);
Vậy thì rốt cuộc ví dụ ban đầu ở trên hoạt động như thế nào?
- Tích lũy tham số: Mỗi bản cập nhật thêm các tham số của nó vào accumulatedUpdates.current, hợp nhất với bất kỳ tham số hiện có nào.
- Thực thi Debounce: processUpdates đợi delay mili giây không hoạt động trước khi thực thi.
- Gọi API: Sau khi thời gian debounced trôi qua, apiUpdate được gọi với các tham số được hợp nhất.
- Đặt lại bản cập nhật tích lũy: Sau khi gọi API, chúng ta đặt lại accumulatedUpdates.current thành null.
Ví dụ sử dụng Dưới đây là cách bạn có thể sử dụng hook này trong một component:
import React from "react";
import useDebouncedUpdate from "./useDebouncedUpdate";
function SettingsComponent() {
const debouncedUpdate = useDebouncedUpdate(updateSettingsApi, 500);
const handleChange = (settingName, value) => {
debouncedUpdate({
id: "user-settings",
params: { [settingName]: value },
});
};
return (
<div>
<input
type="text"
onChange={(e) => handleChange("username", e.target.value)}
/>
<input
type="checkbox"
onChange={(e) => handleChange("notifications", e.target.checked)}
/>
</div>
);
}
function updateSettingsApi({ id, params }) {
// Make your API call here
console.log("Updating settings:", params);
}
- Hành động của người dùng: Khi người dùng nhập hoặc chuyển đổi cài đặt, handleChange được gọi.
- Bản cập nhật Debounced: Các thay đổi được tích lũy và gửi đến API sau 500ms không hoạt động.
Kết luận
Hook này là giải pháp cho một thách thức mà chúng tôi gặp phải trong dự án của mình. Trong trình chỉnh sửa trực quan, khi bạn di chuyển xung quanh canvas, nó sẽ kích hoạt các yêu cầu đồng thời để cập nhật cả offset và mức thu phóng. Điều này cho phép chúng tôi không chỉ gửi một yêu cầu thay vì hai yêu cầu mà còn debounce các bản cập nhật vì sự kiện chuột được kích hoạt nhiều lần.
Bằng cách kết hợp debouncing với tích lũy trạng thái, chúng ta có thể tạo ra các ứng dụng hiệu quả và phản hồi nhanh. Hook useDebouncedUpdate đảm bảo rằng các thay đổi nhanh chóng được gom lại với nhau, giảm các lệnh gọi API không cần thiết và cải thiện hiệu suất. Cảm ơn các bạn đã theo dõi.
All rights reserved