TTMT.ManageWebGUI/SERVICES_VS_HOOKS.md
2025-12-22 14:53:19 +07:00

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