برنامه نویسی

تسلط بر React Query ساده سازی مدیریت داده ها در React with Separation Patterns

Summarize this content to 400 words in Persian Lang

در اکثر برنامه های فرانت اند، React Query به عنوان پلی بین سرور و فرانت اند عمل می کند. با آن، شما نه تنها می توانید داده ها را بخوانید، بلکه داده های سرور را نیز تغییر دهید. هرچه بیشتر از React Query استفاده کنیم، بیشتر باید به انتزاع برای ساده سازی و کپسوله کردن کد فکر کنیم.

چند سال پیش، با Redux، ما بیشتر منطق کسب و کار را برای واکشی داده ها در سطح کاهنده ذخیره کردیم، که یک لایه انتزاعی خوب ارائه داد که در آن می‌توانیم منطق تجاری برنامه را از مؤلفه‌ها جدا کنیم. با این حال، با React Query، کامپوننت‌ها با منطق در مورد چگونگی و زمان واکشی داده‌ها، کارهایی که باید هنگام به‌روزرسانی وضعیت سرور انجام شود و غیره شروع به رشد کردند. این مشکل کپسوله سازی و ساده سازی را دوباره مطرح کرده است.

این مقاله روش‌هایی را برای بازگرداندن اجزای شما به حالت واضح‌تر و نحوه مدیریت منطق واکشی را شرح می‌دهد، به‌ویژه در مورد نحوه استفاده از الگوهای «جدایی پرس و جوی رایج» و «جداسازی نگرانی‌ها» با React بحث می‌کند.

قلاب های سفارشی در React به عنوان راه حلی برای انتزاع منطق از اجزای عملکردی معرفی شدند. آنها به شما اجازه می دهند بخشی از مدیریت دولتی یا منطق تجاری را به قلاب های قابل استفاده مجدد منتقل کنید. با این حال، بدون هدف مشخص، ایجاد بسیاری از قلاب های سفارشی می تواند پروژه را پیچیده کند. از سوی دیگر، استفاده عاقلانه از قلاب های سفارشی می تواند مزایای قابل توجهی را برای پایگاه کد شما به ارمغان بیاورد.

در React، تفاوت بین یک هوک سفارشی و یک تابع معمولی در استفاده از قلاب‌های React نهفته است useState، useEffectو غیره. به زبان ساده، قلاب‌های سفارشی از روش‌های چرخه عمر React استفاده می‌کنند، در حالی که توابع معمولی اینطور نیستند.

export const useFetchUsers = () => {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Function to fetch users from the API
const fetchUsers = async () => {
try {
const response = await axios.get(‘https://jsonplaceholder.typicode.com/users’);
setUsers(response.data);
setIsLoading(false);
} catch (err) {
setError(err);
setIsLoading(false);
}
};

fetchUsers();
}, []);

return { users, isLoading, error };
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به همین دلیل است که می‌توانیم قلاب‌های سفارشی را به‌عنوان توابع در نظر بگیریم و به این فکر کنیم که چگونه منطق را در آنها انتزاع کنیم، درست مانند کلاس‌ها یا توابع معمولی.

قلاب‌های React Query عملکرد گسترده‌ای را ارائه می‌کنند و می‌توانند نمونه زیر را تنها با چند خط کد جایگزین کنند:

export const useUsersQuery = () => {
return useQuery(
queryKey: usersQueryKeys.list(),
queryFn: fetchUsers
);
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر علاقه مند به ایجاد پرس و جو و استفاده مجدد بیشتر از آنها هستید، می توانید جزئیات بیشتری را در مقاله من Mastering React Query بیابید. ساختار کد خود را برای مقیاس پذیری و قابلیت استفاده مجدد

در واقعیت، کار با React Query اغلب به منطق اضافی پس از واکشی داده ها نیاز دارد. به عنوان مثال:

پرس و جوهای تودرتو: جایی که یک پرس و جو به دیگری بستگی دارد.
تبدیل داده ها
اعتبارسنجی داده ها از سرور
واکشی مجدد داده ها هنگام تغییر چیزی (مانند تغییر فیلتر).

البته، شما می‌توانید تمام این منطق را در کامپوننتی که React Query Hook می‌نامید ذخیره کنید، اما این رویکرد اغلب به اجزایی با منطق پیچیده زیادی منجر می‌شود که پیگیری آنها را دشوار می‌کند و قابلیت استفاده مجدد را کاهش می‌دهد.

بیایید نمونه ای از کامپوننتی را ببینیم که از React Query بدون قلاب های سفارشی استفاده می کند:

const UsersComponent = () => {
const { data: users, error, isLoading } = useQuery([‘users’], fetchUsers, {
staleTime: 30000, // Cache data for 30 seconds
refetchOnWindowFocus: false, // Disable refetch on window focus
});

// Data transformation
const activeUsers = users?.filter(user => user.isActive);

if (isLoading) return p>Loading…/p>;
if (error) return p>Error loading users: {error.message}/p>;

return (
ul>
{activeUsers.map(user => (
li key={user.id}>
{user.name} ({user.email})
/li>
))}
/ul>
);
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نگاه اول، این کد خوب به نظر می رسد، اما در یک پروژه پیچیده، منطق این کامپوننت می تواند به سرعت رشد کند و پیروی از آن را دشوارتر کند و قابلیت استفاده مجدد را کاهش دهد.

حال، بیایید به این مثال با انتزاع قلاب سفارشی نگاه کنیم:

const useFetchActiveUsers = () => {
const query = useQuery([‘users’], fetchUsers, {
staleTime: 30000, // Cache data for 30 seconds
refetchOnWindowFocus: false, // Disable refetch on window focus
});

// Additional logic: filtering active users
const activeUsers = query.data?.filter(user => user.isActive);

return { …query, data: activeUsers };
};

const UsersComponent = () => {
const { data: activeUsers, error, isLoading } = useActiveUsersQuery();

if (isLoading) return p>Loading…/p>;
if (error) return p>Error loading users: {error.message}/p>;

return (
ul>
{activeUsers.map(user => (
li key={user.id}>
{user.name} ({user.email})
/li>
))}
/ul>
);
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما الگوی «تفکیک نگرانی‌ها» یا «اصل مسئولیت واحد» را اعمال کردیم، جایی که همه منطق واکشی را به خارج از مؤلفه UI منتقل کردیم. اکنون، کامپوننت صرفاً بر روی رندر کردن رابط کاربری متمرکز شده است.

هنگام کار با API و React Query، دو قلاب اصلی وجود دارد: useQuery و useMutation. این useQuery هوک داده ها را واکشی می کند و از کش برای بهینه سازی منطق واکشی استفاده می کند. این useMutation هوک در درجه اول برای جهش داده است. این قلاب ها متفاوت عمل می کنند و جداسازی این قلاب ها عمدی است.

در پایگاه‌های کد دنیای واقعی، هنگام انتزاع React Query، معمول است که یک هوک داشته باشیم که در آن داده‌ها واکشی و جهش داده‌ها اتفاق می‌افتد:

export const useUser = (userId) => {
const queryClient = useQueryClient();

// Fetch all users
const usersQuery = useQuery([‘users’], fetchUsers, {
staleTime: 30000, // Cache data for 30 seconds
refetchOnWindowFocus: false, // Disable refetch on window focus
});

// Fetch a single user by ID (optional)
const userQuery = useQuery([‘user’, userId], () => fetchUserById(userId), {
enabled: !!userId, // Only run this query if a userId is provided
});

// Mutation for updating user data
const mutation = useMutation(updateUser, {
onSuccess: () => {
// Invalidate and refetch users data after a successful update
queryClient.invalidateQueries([‘users’]);
if (userId) {
queryClient.invalidateQueries([‘user’, userId]);
}
},
});

// Function to update a user
const updateUserDetails = (userId, updatedData) => {
mutation.mutate({ userId, updatedData });
};

return {
users: usersQuery.data,
isLoadingUsers: usersQuery.isLoading,
isErrorUsers: usersQuery.isError,
user: userQuery.data,
isLoadingUser: userQuery.isLoading,
isErrorUser: userQuery.isError,
updateUserDetails,
isUpdating: mutation.isLoading,
updateError: mutation.isError,
};
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نگاه اول، هیچ مشکلی در این قلاب وجود ندارد، اما “به روز رسانی” داده ها اغلب به منطق بیشتری نیاز دارد، مانند آماده کردن داده ها برای سرور، رسیدگی به خطاهای خاص مانند اعتبار سنجی و غیره.

یکی از الگوهایی که می‌توانیم در اینجا اعمال کنیم «جداسازی پرس و جوی رایج» است که معمولاً در قسمت پشتیبان استفاده می‌شود.

جداسازی پرس و جوی دستوری (CQS) یک اصل طراحی است که در توسعه نرم افزار، به ویژه در برنامه نویسی شی گرا، استفاده می شود و بین روش هایی که اعمال (فرمان ها) را انجام می دهند و روش هایی که داده ها را برمی گرداند (پرس و جوها) تمایز قائل می شود.

اصل اصلی در اینجا جداسازی است get منطق از mutation (یا commands در زمینه اصلی). در React، ما مستقیماً داده‌های API را تغییر نمی‌دهیم، اما با React Query، این جداسازی را با استفاده از useQuery و useMutation قلاب ها

پس از اعمال این الگو، می‌توانیم آن را تقسیم کنیم useUser به دو قلاب مجزا قلاب کنید:

const useUserQuery = (userId) => {
// Fetch a single user by ID (optional)
const {data: user, isLoading, isError} = useQuery([‘user’, userId], () => fetchUserById(userId), {
enabled: !!userId, // Only run this query if a userId is provided
});

return {
user,
isLoading,
isError,
};
};

const useUserMutation = () => {
const queryClient = useQueryClient();

// Mutation for updating user data
const mutation = useMutation(updateUser, {
onSuccess: (_, { userId }) => {
// Invalidate and refetch users data after a successful update
queryClient.invalidateQueries([‘users’]);
if (userId) {
queryClient.invalidateQueries([‘user’, userId]);
}
},
});

return {
updateUserDetails: mutation.mutate,
isUpdating: mutation.isLoading,
isError: mutation.isError,
};
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این جدایی برای ما مزایایی مانند:

مدولار بودن: هر قلاب بر روی یک مسئولیت متمرکز است و مدیریت و آزمایش منطق را آسان تر می کند.

قابلیت استفاده مجدد: می توانید دوباره استفاده کنید useUserQuery و useUserMutation به طور مستقل در سایر بخش های برنامه شما.

مقیاس پذیری: همانطور که برنامه شما رشد می کند، جداسازی نگرانی ها به حفظ و مقیاس بندی کارآمد پایگاه کد کمک می کند.

برای نشان دادن کارهایی که این قلاب انجام می‌دهد به دیگر توسعه‌دهندگان، بهتر است که postfix را با توضیحاتی که این هوک انجام می‌دهد اضافه کنید. می تواند باشد Query و Mutation یا پسوندهای مرتبط بیشتر به پایگاه کد شما.

توسعه دهندگان می توانند با مشکل اجزای پیچیده مواجه شوند که ترکیبی از منطق مختلف و رندر رابط کاربری بزرگ ترین بخش آن نیست. با استفاده از الگوهای مانند تفکیک نگرانی ها و جداسازی پرس و جو رایج، می توانید یک معماری تمیز و ماژولار ایجاد کنید که اجزای شما را بر روی رندر رابط کاربری متمرکز نگه می دارد و در عین حال منطق واکشی داده و جهش را به قلاب های سفارشی واگذار می کند.

گنجاندن این اصول در گردش کار توسعه React به شما کمک می‌کند تا پیچیدگی همگام‌سازی حالت و سرور را به طور مؤثر مدیریت کنید و اطمینان حاصل کنید که برنامه شما در طول تکامل به آسانی قابل درک و گسترش است.

در اکثر برنامه های فرانت اند، React Query به عنوان پلی بین سرور و فرانت اند عمل می کند. با آن، شما نه تنها می توانید داده ها را بخوانید، بلکه داده های سرور را نیز تغییر دهید. هرچه بیشتر از React Query استفاده کنیم، بیشتر باید به انتزاع برای ساده سازی و کپسوله کردن کد فکر کنیم.

چند سال پیش، با Redux، ما بیشتر منطق کسب و کار را برای واکشی داده ها در سطح کاهنده ذخیره کردیم، که یک لایه انتزاعی خوب ارائه داد که در آن می‌توانیم منطق تجاری برنامه را از مؤلفه‌ها جدا کنیم. با این حال، با React Query، کامپوننت‌ها با منطق در مورد چگونگی و زمان واکشی داده‌ها، کارهایی که باید هنگام به‌روزرسانی وضعیت سرور انجام شود و غیره شروع به رشد کردند. این مشکل کپسوله سازی و ساده سازی را دوباره مطرح کرده است.

این مقاله روش‌هایی را برای بازگرداندن اجزای شما به حالت واضح‌تر و نحوه مدیریت منطق واکشی را شرح می‌دهد، به‌ویژه در مورد نحوه استفاده از الگوهای «جدایی پرس و جوی رایج» و «جداسازی نگرانی‌ها» با React بحث می‌کند.

قلاب های سفارشی در React به عنوان راه حلی برای انتزاع منطق از اجزای عملکردی معرفی شدند. آنها به شما اجازه می دهند بخشی از مدیریت دولتی یا منطق تجاری را به قلاب های قابل استفاده مجدد منتقل کنید. با این حال، بدون هدف مشخص، ایجاد بسیاری از قلاب های سفارشی می تواند پروژه را پیچیده کند. از سوی دیگر، استفاده عاقلانه از قلاب های سفارشی می تواند مزایای قابل توجهی را برای پایگاه کد شما به ارمغان بیاورد.

در React، تفاوت بین یک هوک سفارشی و یک تابع معمولی در استفاده از قلاب‌های React نهفته است useState، useEffectو غیره. به زبان ساده، قلاب‌های سفارشی از روش‌های چرخه عمر React استفاده می‌کنند، در حالی که توابع معمولی اینطور نیستند.

export const useFetchUsers = () => {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Function to fetch users from the API
    const fetchUsers = async () => {
      try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/users');
        setUsers(response.data);
        setIsLoading(false);
      } catch (err) {
        setError(err);
        setIsLoading(false);
      }
    };

    fetchUsers();
  }, []);

  return { users, isLoading, error };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به همین دلیل است که می‌توانیم قلاب‌های سفارشی را به‌عنوان توابع در نظر بگیریم و به این فکر کنیم که چگونه منطق را در آنها انتزاع کنیم، درست مانند کلاس‌ها یا توابع معمولی.

قلاب‌های React Query عملکرد گسترده‌ای را ارائه می‌کنند و می‌توانند نمونه زیر را تنها با چند خط کد جایگزین کنند:

export const useUsersQuery = () => {
  return useQuery(
      queryKey: usersQueryKeys.list(),
      queryFn: fetchUsers
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر علاقه مند به ایجاد پرس و جو و استفاده مجدد بیشتر از آنها هستید، می توانید جزئیات بیشتری را در مقاله من Mastering React Query بیابید. ساختار کد خود را برای مقیاس پذیری و قابلیت استفاده مجدد

در واقعیت، کار با React Query اغلب به منطق اضافی پس از واکشی داده ها نیاز دارد. به عنوان مثال:

  • پرس و جوهای تودرتو: جایی که یک پرس و جو به دیگری بستگی دارد.
  • تبدیل داده ها
  • اعتبارسنجی داده ها از سرور
  • واکشی مجدد داده ها هنگام تغییر چیزی (مانند تغییر فیلتر).

البته، شما می‌توانید تمام این منطق را در کامپوننتی که React Query Hook می‌نامید ذخیره کنید، اما این رویکرد اغلب به اجزایی با منطق پیچیده زیادی منجر می‌شود که پیگیری آنها را دشوار می‌کند و قابلیت استفاده مجدد را کاهش می‌دهد.

بیایید نمونه ای از کامپوننتی را ببینیم که از React Query بدون قلاب های سفارشی استفاده می کند:

const UsersComponent = () => {
  const { data: users, error, isLoading } = useQuery(['users'], fetchUsers, {
    staleTime: 30000,  // Cache data for 30 seconds
    refetchOnWindowFocus: false, // Disable refetch on window focus
  });

  // Data transformation
  const activeUsers = users?.filter(user => user.isActive);

  if (isLoading) return p>Loading.../p>;
  if (error) return p>Error loading users: {error.message}/p>;

  return (
    ul>
      {activeUsers.map(user => (
        li key={user.id}>
          {user.name} ({user.email})
        /li>
      ))}
    /ul>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نگاه اول، این کد خوب به نظر می رسد، اما در یک پروژه پیچیده، منطق این کامپوننت می تواند به سرعت رشد کند و پیروی از آن را دشوارتر کند و قابلیت استفاده مجدد را کاهش دهد.

حال، بیایید به این مثال با انتزاع قلاب سفارشی نگاه کنیم:

const useFetchActiveUsers = () => {
  const query = useQuery(['users'], fetchUsers, {
    staleTime: 30000,  // Cache data for 30 seconds
    refetchOnWindowFocus: false, // Disable refetch on window focus
  });

  // Additional logic: filtering active users
  const activeUsers = query.data?.filter(user => user.isActive);

  return { ...query, data: activeUsers };
};

const UsersComponent = () => {
  const { data: activeUsers, error, isLoading } = useActiveUsersQuery();

  if (isLoading) return p>Loading.../p>;
  if (error) return p>Error loading users: {error.message}/p>;

  return (
    ul>
      {activeUsers.map(user => (
        li key={user.id}>
          {user.name} ({user.email})
        /li>
      ))}
    /ul>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما الگوی «تفکیک نگرانی‌ها» یا «اصل مسئولیت واحد» را اعمال کردیم، جایی که همه منطق واکشی را به خارج از مؤلفه UI منتقل کردیم. اکنون، کامپوننت صرفاً بر روی رندر کردن رابط کاربری متمرکز شده است.

هنگام کار با API و React Query، دو قلاب اصلی وجود دارد: useQuery و useMutation. این useQuery هوک داده ها را واکشی می کند و از کش برای بهینه سازی منطق واکشی استفاده می کند. این useMutation هوک در درجه اول برای جهش داده است. این قلاب ها متفاوت عمل می کنند و جداسازی این قلاب ها عمدی است.

در پایگاه‌های کد دنیای واقعی، هنگام انتزاع React Query، معمول است که یک هوک داشته باشیم که در آن داده‌ها واکشی و جهش داده‌ها اتفاق می‌افتد:


export const useUser = (userId) => {
  const queryClient = useQueryClient();

  // Fetch all users
  const usersQuery = useQuery(['users'], fetchUsers, {
    staleTime: 30000,  // Cache data for 30 seconds
    refetchOnWindowFocus: false, // Disable refetch on window focus
  });

  // Fetch a single user by ID (optional)
  const userQuery = useQuery(['user', userId], () => fetchUserById(userId), {
    enabled: !!userId, // Only run this query if a userId is provided
  });

  // Mutation for updating user data
  const mutation = useMutation(updateUser, {
    onSuccess: () => {
      // Invalidate and refetch users data after a successful update
      queryClient.invalidateQueries(['users']);
      if (userId) {
        queryClient.invalidateQueries(['user', userId]);
      }
    },
  });

  // Function to update a user
  const updateUserDetails = (userId, updatedData) => {
    mutation.mutate({ userId, updatedData });
  };

  return {
    users: usersQuery.data,
    isLoadingUsers: usersQuery.isLoading,
    isErrorUsers: usersQuery.isError,
    user: userQuery.data,
    isLoadingUser: userQuery.isLoading,
    isErrorUser: userQuery.isError,
    updateUserDetails,
    isUpdating: mutation.isLoading,
    updateError: mutation.isError,
  };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نگاه اول، هیچ مشکلی در این قلاب وجود ندارد، اما “به روز رسانی” داده ها اغلب به منطق بیشتری نیاز دارد، مانند آماده کردن داده ها برای سرور، رسیدگی به خطاهای خاص مانند اعتبار سنجی و غیره.

یکی از الگوهایی که می‌توانیم در اینجا اعمال کنیم «جداسازی پرس و جوی رایج» است که معمولاً در قسمت پشتیبان استفاده می‌شود.

جداسازی پرس و جوی دستوری (CQS) یک اصل طراحی است که در توسعه نرم افزار، به ویژه در برنامه نویسی شی گرا، استفاده می شود و بین روش هایی که اعمال (فرمان ها) را انجام می دهند و روش هایی که داده ها را برمی گرداند (پرس و جوها) تمایز قائل می شود.

اصل اصلی در اینجا جداسازی است get منطق از mutation (یا commands در زمینه اصلی). در React، ما مستقیماً داده‌های API را تغییر نمی‌دهیم، اما با React Query، این جداسازی را با استفاده از useQuery و useMutation قلاب ها

پس از اعمال این الگو، می‌توانیم آن را تقسیم کنیم useUser به دو قلاب مجزا قلاب کنید:

const useUserQuery = (userId) => {
  // Fetch a single user by ID (optional)
  const {data: user, isLoading, isError} = useQuery(['user', userId], () => fetchUserById(userId), {
    enabled: !!userId, // Only run this query if a userId is provided
  });

  return {
    user,
    isLoading,
    isError,
  };
};

const useUserMutation = () => {
  const queryClient = useQueryClient();

  // Mutation for updating user data
  const mutation = useMutation(updateUser, {
    onSuccess: (_, { userId }) => {
      // Invalidate and refetch users data after a successful update
      queryClient.invalidateQueries(['users']);
      if (userId) {
        queryClient.invalidateQueries(['user', userId]);
      }
    },
  });

  return {
    updateUserDetails: mutation.mutate,
    isUpdating: mutation.isLoading,
    isError: mutation.isError,
  };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این جدایی برای ما مزایایی مانند:

  • مدولار بودن: هر قلاب بر روی یک مسئولیت متمرکز است و مدیریت و آزمایش منطق را آسان تر می کند.
  • قابلیت استفاده مجدد: می توانید دوباره استفاده کنید useUserQuery و useUserMutation به طور مستقل در سایر بخش های برنامه شما.
  • مقیاس پذیری: همانطور که برنامه شما رشد می کند، جداسازی نگرانی ها به حفظ و مقیاس بندی کارآمد پایگاه کد کمک می کند.

برای نشان دادن کارهایی که این قلاب انجام می‌دهد به دیگر توسعه‌دهندگان، بهتر است که postfix را با توضیحاتی که این هوک انجام می‌دهد اضافه کنید. می تواند باشد Query و Mutation یا پسوندهای مرتبط بیشتر به پایگاه کد شما.

توسعه دهندگان می توانند با مشکل اجزای پیچیده مواجه شوند که ترکیبی از منطق مختلف و رندر رابط کاربری بزرگ ترین بخش آن نیست. با استفاده از الگوهای مانند تفکیک نگرانی ها و جداسازی پرس و جو رایج، می توانید یک معماری تمیز و ماژولار ایجاد کنید که اجزای شما را بر روی رندر رابط کاربری متمرکز نگه می دارد و در عین حال منطق واکشی داده و جهش را به قلاب های سفارشی واگذار می کند.

گنجاندن این اصول در گردش کار توسعه React به شما کمک می‌کند تا پیچیدگی همگام‌سازی حالت و سرور را به طور مؤثر مدیریت کنید و اطمینان حاصل کنید که برنامه شما در طول تکامل به آسانی قابل درک و گسترش است.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا