# 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 { const response = await axios.get( 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({ 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
Loading...
return software?.map(item =>
{item.name}
) } ``` --- ## 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 { 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([]); 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 ( ); } ``` --- ## 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
{/* ... */}
; } ``` ### ❌ 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