329 lines
9.3 KiB
Markdown
329 lines
9.3 KiB
Markdown
|
|
# Khác biệt giữa Services và Query Hooks
|
||
|
|
|
||
|
|
## Tóm tắt nhanh
|
||
|
|
|
||
|
|
| Aspect | Services | Query Hooks |
|
||
|
|
|--------|----------|-------------|
|
||
|
|
| **Location** | `src/services/` | `src/hooks/queries/` |
|
||
|
|
| **Mục đích** | Gọi API trực tiếp | Wrapper TanStack Query |
|
||
|
|
| **Caching** | ❌ Không | ✅ Có |
|
||
|
|
| **Background Refetch** | ❌ Không | ✅ Có |
|
||
|
|
| **Auto Invalidation** | ❌ Không | ✅ Có |
|
||
|
|
| **Type** | Async functions | React Hooks |
|
||
|
|
| **Dùng trong** | Non-React code, utilities | React components |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Chi tiết Từng Layer
|
||
|
|
|
||
|
|
### 1. Services Layer (`src/services/`)
|
||
|
|
|
||
|
|
**Mục đích:** Đơn thuần gọi API và trả về dữ liệu
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// app-version.service.ts
|
||
|
|
export async function getSoftwareList(): Promise<Version[]> {
|
||
|
|
const response = await axios.get<Version[]>(
|
||
|
|
API_ENDPOINTS.APP_VERSION.GET_SOFTWARE
|
||
|
|
);
|
||
|
|
return response.data;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Đặc điểm:**
|
||
|
|
- ✅ Pure async functions
|
||
|
|
- ✅ Không phụ thuộc vào React
|
||
|
|
- ✅ Có thể sử dụng ở bất kỳ đâu (utils, servers, non-React code)
|
||
|
|
- ❌ Không có caching
|
||
|
|
- ❌ Phải tự quản lý state loading/error
|
||
|
|
- ❌ Phải tự gọi lại khi dữ liệu thay đổi
|
||
|
|
|
||
|
|
**Khi nào dùng:**
|
||
|
|
```typescript
|
||
|
|
// Dùng trong utility functions
|
||
|
|
export async function initializeApp() {
|
||
|
|
const software = await appVersionService.getSoftwareList();
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hoặc trong services khác
|
||
|
|
export async function validateSoftware() {
|
||
|
|
const list = await appVersionService.getSoftwareList();
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. Query Hooks Layer (`src/hooks/queries/`)
|
||
|
|
|
||
|
|
**Mục đích:** Wrapper TanStack Query bên trên services
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// useAppVersionQueries.ts
|
||
|
|
export function useGetSoftwareList(enabled = true) {
|
||
|
|
return useQuery<Version[]>({
|
||
|
|
queryKey: ["app-version", "software"],
|
||
|
|
queryFn: () => appVersionService.getSoftwareList(),
|
||
|
|
enabled,
|
||
|
|
staleTime: 60 * 1000, // 1 minute
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Đặc điểm:**
|
||
|
|
- ✅ React hooks
|
||
|
|
- ✅ Automatic caching
|
||
|
|
- ✅ Background refetching
|
||
|
|
- ✅ Automatic invalidation sau mutations
|
||
|
|
- ✅ Built-in loading/error states
|
||
|
|
- ✅ Deduplication (gộp requests giống nhau)
|
||
|
|
- ❌ Chỉ dùng được trong React components
|
||
|
|
- ❌ Phức tạp hơn services
|
||
|
|
|
||
|
|
**Khi nào dùng:**
|
||
|
|
```typescript
|
||
|
|
// Dùng trong React components
|
||
|
|
function SoftwareList() {
|
||
|
|
const { data: software, isLoading } = useGetSoftwareList()
|
||
|
|
|
||
|
|
if (isLoading) return <div>Loading...</div>
|
||
|
|
return software?.map(item => <div>{item.name}</div>)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## So sánh cụ thể
|
||
|
|
|
||
|
|
### Ví dụ 1: Lấy danh sách
|
||
|
|
|
||
|
|
**Service - Raw API call:**
|
||
|
|
```typescript
|
||
|
|
// services/app-version.service.ts
|
||
|
|
export async function getSoftwareList(): Promise<Version[]> {
|
||
|
|
const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_SOFTWARE);
|
||
|
|
return response.data;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Hook - TanStack Query wrapper:**
|
||
|
|
```typescript
|
||
|
|
// hooks/queries/useAppVersionQueries.ts
|
||
|
|
export function useGetSoftwareList(enabled = true) {
|
||
|
|
return useQuery({
|
||
|
|
queryKey: ["app-version", "software"],
|
||
|
|
queryFn: () => appVersionService.getSoftwareList(),
|
||
|
|
staleTime: 60 * 1000,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Sử dụng trong component:**
|
||
|
|
```typescript
|
||
|
|
function Component() {
|
||
|
|
// ❌ KHÔNG nên dùng service trực tiếp
|
||
|
|
const [data, setData] = useState<Version[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
useEffect(() => {
|
||
|
|
appVersionService.getSoftwareList().then(d => {
|
||
|
|
setData(d);
|
||
|
|
setLoading(false);
|
||
|
|
});
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// ✅ NÊN dùng hook thay vì
|
||
|
|
const { data, isLoading } = useGetSoftwareList();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Ví dụ 2: Upload file
|
||
|
|
|
||
|
|
**Service:**
|
||
|
|
```typescript
|
||
|
|
// services/app-version.service.ts
|
||
|
|
export async function uploadSoftware(
|
||
|
|
formData: FormData,
|
||
|
|
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
|
||
|
|
): Promise<{ message: string }> {
|
||
|
|
return axios.post(API_ENDPOINTS.APP_VERSION.UPLOAD, formData, {
|
||
|
|
onUploadProgress,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Hook:**
|
||
|
|
```typescript
|
||
|
|
// hooks/queries/useAppVersionQueries.ts
|
||
|
|
export function useUploadSoftware() {
|
||
|
|
const queryClient = useQueryClient();
|
||
|
|
|
||
|
|
return useMutation({
|
||
|
|
mutationFn: (data: { formData: FormData; onUploadProgress?: ... }) =>
|
||
|
|
appVersionService.uploadSoftware(data.formData, data.onUploadProgress),
|
||
|
|
onSuccess: () => {
|
||
|
|
// Tự động invalidate software list
|
||
|
|
queryClient.invalidateQueries({
|
||
|
|
queryKey: ["app-version", "software"],
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Sử dụng:**
|
||
|
|
```typescript
|
||
|
|
function UploadForm() {
|
||
|
|
const uploadMutation = useUploadSoftware();
|
||
|
|
|
||
|
|
const handleUpload = async (file: File) => {
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append("file", file);
|
||
|
|
|
||
|
|
await uploadMutation.mutateAsync({
|
||
|
|
formData,
|
||
|
|
onUploadProgress: (e) => console.log(`${e.loaded}/${e.total}`)
|
||
|
|
});
|
||
|
|
|
||
|
|
// ✅ Software list tự động update
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<button disabled={uploadMutation.isPending}>
|
||
|
|
{uploadMutation.isPending ? "Uploading..." : "Upload"}
|
||
|
|
</button>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ React Component │
|
||
|
|
│ (SoftwareList, UploadForm, etc.) │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│
|
||
|
|
│ uses
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Query Hooks (src/hooks/queries/) │
|
||
|
|
│ - useGetSoftwareList() │
|
||
|
|
│ - useUploadSoftware() │
|
||
|
|
│ - useDeleteBlacklist() │
|
||
|
|
│ - Features: │
|
||
|
|
│ - Caching │
|
||
|
|
│ - Auto invalidation │
|
||
|
|
│ - Loading states │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│
|
||
|
|
│ wraps
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Service Functions (src/services/) │
|
||
|
|
│ - getSoftwareList() │
|
||
|
|
│ - uploadSoftware() │
|
||
|
|
│ - deleteBlacklist() │
|
||
|
|
│ - Features: │
|
||
|
|
│ - Pure async functions │
|
||
|
|
│ - Direct API calls │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│
|
||
|
|
│ uses
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Axios (HTTP Client) │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│
|
||
|
|
│ requests
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Backend API Server │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Nguyên tắc sử dụng
|
||
|
|
|
||
|
|
### ✅ NÊN dùng Services khi:
|
||
|
|
- Gọi API từ non-React code (utilities, event handlers, etc.)
|
||
|
|
- Cần gọi API một lần rồi không cần tracking state
|
||
|
|
- Không cần caching hay background refetch
|
||
|
|
- Viết code không phụ thuộc React
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ✅ OK - utility function
|
||
|
|
export async function syncData() {
|
||
|
|
const software = await appVersionService.getSoftwareList();
|
||
|
|
const commands = await commandService.getCommandList();
|
||
|
|
return { software, commands };
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### ✅ NÊN dùng Hooks khi:
|
||
|
|
- Lấy/update dữ liệu trong React components
|
||
|
|
- Cần caching và background refetch
|
||
|
|
- Muốn dữ liệu tự động update
|
||
|
|
- Cần tracking loading/error states
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ✅ OK - React component
|
||
|
|
function Dashboard() {
|
||
|
|
const { data: software, isLoading } = useGetSoftwareList();
|
||
|
|
const uploadMutation = useUploadSoftware();
|
||
|
|
|
||
|
|
return <div>{/* ... */}</div>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### ❌ KHÔNG nên dùng Services khi:
|
||
|
|
- Đang trong React component và cần state management
|
||
|
|
- Cần automatic refetching
|
||
|
|
- Cần auto-invalidation sau mutations
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ WRONG
|
||
|
|
function Component() {
|
||
|
|
const [data, setData] = useState([]);
|
||
|
|
useEffect(() => {
|
||
|
|
appVersionService.getSoftwareList().then(setData);
|
||
|
|
}, []);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✅ RIGHT
|
||
|
|
function Component() {
|
||
|
|
const { data } = useGetSoftwareList();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### ❌ KHÔNG nên dùng Hooks khi:
|
||
|
|
- Không phải trong React component
|
||
|
|
- Không có React context
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ WRONG - không thể gọi hooks ở đây
|
||
|
|
export function initApp() {
|
||
|
|
const { data } = useGetSoftwareList(); // ERROR!
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✅ RIGHT
|
||
|
|
export async function initApp() {
|
||
|
|
const data = await appVersionService.getSoftwareList();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
**Services** = Cơ sở API calls, có thể tái sử dụng ở bất kỳ đâu
|
||
|
|
**Hooks** = Lớp React trên services, tối ưu cho React components
|
||
|
|
|
||
|
|
**Dùng Services** khi bạn cần tính linh hoạt và độc lập với React
|
||
|
|
**Dùng Hooks** khi bạn muốn TanStack Query quản lý state và caching tự động
|