1. 22:00 09/10/2026
Khi xây dựng ứng dụng đa nền tảng, việc chia sẻ logic upload file giữa React web và React Native có thể giúp tiết kiệm thời gian đáng kể và đảm bảo trải nghiệm người dùng đồng nhất. Dưới đây là hướng dẫn từng bước để tạo một hệ thống upload file linh hoạt, có thể tái sử dụng, tận dụng các công cụ hiện đại như React Query, Context API và custom hooks.
**1. Xác định các mối quan tâm cốt lõi**
- **Validation**: Kiểm tra loại tệp, kích thước, tên.
- **Progress Tracking**: Hiển thị thanh tiến trình tải lên.
- **Server Integration**: Gửi yêu cầu upload và xử lý phản hồi.
- **State Management**: Đồng bộ trạng thái giữa các component trên cả hai nền tảng.
**2. Tạo một layer chung cho service
```javascript
// fileUploadService.js
export const uploadFile = async (file, onProgress) => {
const formData = new FormData();
formData.append('file', file);
const { data, progress } = await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (event) => {
if (onProgress) onProgress(Math.round((event.loaded * 100) / event.total));
},
});
return data;
};
```
Service này hoạt động trên cả hai nền tảng vì nó chỉ sử dụng các API mà cả React web (qua axios) và React Native (qua axios hoặc fetch) đều hỗ trợ.
**3. Xây dựng một custom hook để xử lý trạng thái
```javascript
// useFileUpload.js
import { useState, useCallback } from 'react';
import { uploadFile } from './fileUploadService';
import { useQueryClient } from '@tanstack/react-query';
export const useFileUpload = () => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const queryClient = useQueryClient();
const upload = useCallback(async (file) => {
setProgress(0);
setError(null);
try {
const result = await uploadFile(file, (p) => setProgress(p));
// Làm mới các query liên quan để cập nhật danh sách tệp
queryClient.invalidateQueries(['fileList']);
return result;
} catch (err) {
setError(err.message);
throw err;
}
}, [queryClient]);
return { upload, progress, error };
};
```
**4. Tận dụng Context API để đồng bộ trạng thái toàn cục
```javascript
// FileUploadContext.js
import React, { createContext, useContext } from 'react';
import { useFileUpload } from './useFileUpload';
const FileUploadContext = createContext();
export const FileUploadProvider = ({ children }) => {
const uploadHook = useFileUpload();
return (
<FileUploadContext.Provider value={uploadHook}>
{children}
</FileUploadContext.Provider>
);
};
export const useUploadContext = () => useContext(FileUploadContext);
```
**5. Xây dựng component có thể tái sử dụng
Component này có thể được sử dụng trên cả hai nền tảng, chỉ khác nhau về giao diện người dùng (ví dụ: sử dụng styled-components native cho React Native).
```javascript
// FileUploadComponent.js
import { useUploadContext } from './FileUploadContext';
export const FileUploadComponent = () => {
const { upload, progress, error } = useUploadContext();
const handleChange = async (e) => {
const file = e.target.files[0];
if (file) await upload(file);
};
return (
<div>
<input type="file" onChange={handleChange} />
{progress > 0 && <p>Đang tải lên: {progress}%</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
};
```
**6. Tận dụng React Query để xử lý dữ liệu
Bằng cách lưu trữ danh sách tệp trong React Query, bạn có thể tự động đồng bộ hóa dữ liệu giữa các màn hình, nền tảng và thậm chí cả các lần truy cập sau.
**7. Lời khuyên thực tế**
- **Tái sử dụng validation**: Sử dụng một schema duy nhất (ví dụ: Yup) cho cả hai nền tảng.
- **Xử lý lỗi một cách nhất quán**: Hiển thị thông báo lỗi được dịch trên cả hai nền tảng.
- **Tối ưu hóa hiệu suất**: Cache các yêu cầu upload và sử dụng `React Query` để tránh tải lại không cần thiết.
- **Kiểm tra chéo nền tảng**: Sử dụng cùng một bộ kiểm tra Jest cho cả React web và React Native để đảm bảo logic hoạt động chính xác.
Bằng cách tuân theo các mẫu thiết kế này, bạn sẽ có một hệ thống upload file mạnh mẽ, dễ bảo trì và có thể mở rộng trên cả hai nền tảng React web và React Native.