التدفق · فصل ٥

كود المنتج والسلة والزرار

Product · cartQueries · useAddToCart · public API
🎯 الهدف: تشوف كود الـ Entity للمنتج والسلة، وكود فيتشر الإضافة، والاستيراد من الواجهة المعلنة.

المثال بـ 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';
الفيتشر تنفّذ الـ Use Case. الـ Entity تنفّذ عملية الـ API. React Query تدير حالة الطلب وتحديث الكاش.

خلّصت الفصل لما

🧠 اختبر نفسك
١) الزرار يستورد addCartItem من ملف الـ API جوه entities؟
لأ. من @/entities/cart، وده اللي بيصدّره index.ts.
٢) بعد نجاح الإضافة، أنهي مفتاح يتعلّم إنه محتاج تحديث؟
['cart', 'current'].