المثال بـ TanStack Query لإدارة الـ Server State.
المنتج
entities/product/model/types.ts
export interface Product { id: string; title: string; price: number; image: string; stock: number;}entities/product/api/get-product.ts
import { apiClient } from '@/shared/api/client';import type { Product } from '../model/types';export async function getProduct(id: string) { const { data } = await apiClient.get<Product>( `/products/${id}` ); return data;}entities/product/model/queries.ts
import { queryOptions } from '@tanstack/react-query';import { getProduct } from '../api/get-product';export const productQueries = { detail: (id: string) => queryOptions({ queryKey: ['products', 'detail', id], queryFn: () => getProduct(id), }),};entities/product/index.ts
export type { Product } from './model/types';export { productQueries } from './model/queries';export { ProductCard } from './ui/product-card';أي Slice خارجية تستخدم الـ Product من خلال الـ Public API، بدل استيراد الملفات الداخلية بشكل مباشر.
السلة
entities/cart/api/add-cart-item.ts
import { apiClient } from '@/shared/api/client';export interface AddCartItemInput { productId: string; quantity: number;}export async function addCartItem( input: AddCartItemInput) { const { data } = await apiClient.post( '/cart/items', input ); return data;}entities/cart/model/queries.ts
import { queryOptions } from '@tanstack/react-query';import { getCart } from '../api/get-cart';export const cartQueries = { current: () => queryOptions({ queryKey: ['cart', 'current'], queryFn: getCart, }),};entities/cart/index.ts
export { addCartItem } from './api/add-cart-item';export type { AddCartItemInput } from './api/add-cart-item';export { cartQueries } from './model/queries';الـ Entity هنا مسؤول عن تمثيل الـ Cart وتوفير عمليات التعامل معاه، وليس عن شكل زر الإضافة أو طريقة عرضه.
فيتشر الإضافة
features/add-to-cart/model/use-add-to-cart.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';import { addCartItem } from '@/entities/cart';export function useAddToCart() { const queryClient = useQueryClient(); return useMutation({ mutationFn: addCartItem, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ['cart', 'current'], }); }, });}features/add-to-cart/ui/add-to-cart-button.tsx
import { useAddToCart } from '../model/use-add-to-cart';interface Props { productId: string; disabled?: boolean;}export function AddToCartButton({ productId, disabled,}: Props) { const mutation = useAddToCart(); return ( <button disabled={disabled || mutation.isPending} onClick={() => mutation.mutate({ productId, quantity: 1, }) } > {mutation.isPending ? 'Adding...' : 'Add to Cart'} </button> );}features/add-to-cart/index.ts
export { AddToCartButton } from './ui/add-to-cart-button';خلّصت الفصل لما
🧠 اختبر نفسك
١) الزرار يستورد addCartItem من ملف الـ API جوه entities؟
لأ. من
@/entities/cart، وده اللي بيصدّره index.ts.٢) بعد نجاح الإضافة، أنهي مفتاح يتعلّم إنه محتاج تحديث؟
['cart', 'current'].