Anophel-آنوفل Redux Toolkit چیست: بررسی کلی، مثال‌ها و جایگزین‌ها

Redux Toolkit چیست: بررسی کلی، مثال‌ها و جایگزین‌ها

انتشار:
2
0

رابط کاربری ایجاد شده توسط یک برنامه React، Angular، Vue یا React Native تابعی از وضعیت آن است. برای بسیاری از توسعه دهندگان فرانت اند، Redux Toolkit ابزار عالی برای این کار است.Redux Toolkit بخشی از اکوسیستم Redux است. Redux سالهاست که راه حلی برای مدیریت وضعیت برنامه های پیچیده بوده است، اما Redux Toolkit فاصله بین Redux و سهولت استفاده را پر می کند. حدود یک سال است که Redux Toolkit رویکرد رسمی توصیه شده برای استفاده از Redux در برنامه شما بوده است.

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

برای آشنایی با نحوه استفاده از Redux و Redux Toolkit در React می توانید این مقاله را بررسی کنید.

Redux Toolkit چیست؟

Redux Toolkit (RTK) مجموعه ابزار رسمی و معتبر برای توسعه کارآمد Redux است. قبل از RTK، توسعه دهندگان مجبور بودند به صورت دستی فروشگاه های Redux را با استفاده از بسیاری از کدهای boilerplate سیم کشی کنند. این شاملreducer ها، اکشن‌ها، selector ها و میدلور ها فقط برای راه‌اندازی یک Store اولیه بود. RTK با ارائه مجموعه ای از توابع کاربردی که روش استاندارد استفاده از Redux را ساده می کند، این وظایف دردناک را انجام می دهد.

تیم Redux می دانست که توسعه دهندگان با پیچیدگی Redux مشکل دارند. بنابراین، آنها تصمیم گرفتند راه حلی ایجاد کنند که گردش کار Redux را ساده تر کند و مدیریت استیت را برای توسعه دهندگان ساده تر کند و React Toolkit نتیجه آن بود.

Redux Toolkit که در ابتدا در سال 2019 منتشر شد، برای جمع‌بندی بهترین شیوه‌ها، الگوهای رایج و ابزارهای مفید برای بهبود تجربه توسعه Redux ایجاد شد.

Redux Toolkit چگونه کار می کند؟

Redux Toolkit مجموعه ای از اصول Redux است که همه ما می دانیم. این ابزارها را برای ساده کردن وظایفی که توسعه دهندگان از آن متنفرند، ارائه می دهد. در اینجا یک تفکیک سریع وجود دارد:


راه اندازی فروشگاه ساده: تابع configureStore RTK فرآیند ایجاد یک فروشگاه Redux را با تنظیمات از پیش ساخته شده و میدلور ساده می کند.
reducer خودکار و ایجاد اکشن: تابع createSlice به شما امکان می دهد reducer ها و اکشن ها را به سادگی و بدون تمام کدهای قدیمی boilerplate تعریف کنید.
به‌روزرسانی‌های استیت بصری: RTK با کتابخانه Immer ادغام می‌شود، به این معنی که می‌توانید بدون تغییر دستی در استیت بنویسید
میدلور: RTK با Redux Thunk برای اکشن ها ناهمزمان و انتخاب مجدد برای عملکردهای انتخابگر بهینه‌شده ارائه می‌شود.
ابزارهای قدرتمند: React Toolkit همچنین با ابزارهایی مانند RTK Query برای واکشی و ذخیره داده ها ارائه می شود.


تیم Redux اخیراً نسخه 2.0 Redux Toolkit را منتشر کرده است که این ویژگی ها را اضافه کرده است:


یک متد CombinSlices جدید که reducer های slice بار را برای تقسیم کد بهتر تنبل می کند
قابلیت افزودن میدلور در زمان اجرا
پشتیبانی Async thunk در reducer ها
امکان تبدیل selector به بخشی از slice شما

چرا از Redux Toolkit استفاده کنیم؟

بیایید صادق باشیم: Redux باید تغییر کند. این برنامه با کد boilerplate بیش از حد، تنظیمات پیچیده و منحنی یادگیری ارائه شد که می تواند حتی برای توسعه دهندگان باتجربه در قلمرویی که به طور مداوم در حال تغییر هستند، که توسعه frontend می نامیم، شیب دار باشد.


Redux Toolkit تغییر مورد نیاز Redux است. این به جنبه "طرفدار" مقایسه جوانب مثبت و منفی بیشتری اضافه می کند. در اینجا چند دلیل برای استفاده از Redux Toolkit آورده شده است:


راحتی: نوشتن boilerplate بی پایان برای reducer ها، اکشن ها، selector ها و ثابت ها را فراموش کنید. یا با جستجوی نام آن، سپس ثابت، و سپس یافتن مشکل در reducer قبل از فراموش کردن نام اقدام و نیاز به عقب نشینی مجدد از طریق کد، ردیابی چرایی عملکرد درست آن را ردیابی کنید. CreateSlice RTK تمام کارهای سنگین را انجام می دهد و در زمان و سلامت شما صرفه جویی می کند


عملکرد: Redux Toolkit رویکرد تک منبع حقیقت Redux را به ارث می برد و ابزارهایی مانند selector های ذخیره شده و Immer را اضافه می کند، که به شما امکان می دهد وضعیت را به طور تغییرناپذیر تغییر دهید بدون اینکه هزینه ایجاد یک شی جدید برای هر تغییر ایجاد کنید.


سهولت استفاده: RTK به روز رسانی استیت را بصری تر می کند و خطر اشکالات را با Immer کاهش می دهد. API تمیز است، اسناد واقعاً تمام چیزی است که برای استفاده از آن نیاز دارید، و منحنی یادگیری (در مقایسه با وانیلی Redux) ملایم است. من در روزهای اولیه به گزینه های زیادی برای بهبود Redux نگاه کردم که کاری مشابه RTK انجام می داد، چند مورد را امتحان کردم و منصرف شدم. از زمانی که وارد Redux Toolkit شدم، چند سالی است که از آن استفاده کرده‌ام و کاملاً در حال استفاده از آن هستم!


جامعه و اکوسیستم: فقط به این دلیل که دیگران از کتابخانه استفاده می کنند به این معنی نیست که شما باید این کار را انجام دهید. با این حال، یک جامعه بزرگ از RTK پشتیبانی می کند، ابزاری که به خوبی کار می کند. اگر مشکلی دارید یا با پیاده سازی RTK مشکل دارید، معمولاً با جستجوی Google افراد دیگری که نه تنها درد شما را احساس می کنند، پیدا می کنند، بلکه قبلاً راه حل مشکل شما را نیز پیدا کرده اند.


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


ادغام: RTK به خوبی با دیگران، به ویژه React و TypeScript بازی می کند و نیاز به ایجاد تعاریف دستی نوع را کاهش می دهد و در عین حال ایمنی تایپ را به شما ارائه می دهد.
با این حال، Redux Toolkit راه حلی برای همه نیست. در اینجا دلیلی است که ممکن است در استفاده از RTK در برنامه خود تجدید نظر کنید:


اندازه بسته: RTK وزن بیشتری را در مقایسه با وانیلی Redux به باندل شما اضافه می کند. اما بیایید صادق باشیم، در مقایسه با اندازه کلی برنامه های مدرن و مزایایی که به ارمغان می آورد، این هزینه کمی است.


سفارشی سازی: در حالی که سربار را حذف می کند، RTK یک لایه انتزاعی اضافه می کند. اگر به کنترل عمیق بر هر کاری که Redux انجام می دهد نیاز دارید، Redux خالص ممکن است به شما انعطاف بیشتری بدهد. اما برای اکثر برنامه‌ها، نیازی نیست که تا این حد به علف‌های هرز بروید


پیچیدگی: در حالی که Redux Toolkit ساده تر از Redux وانیلی است، هنوز هم منحنی یادگیری دارد، اما منابع زیادی و جامعه بزرگی برای حمایت از شما وجود دارد. در ابتدا می تواند چالش برانگیز باشد، اما زمانی که به آن دست پیدا کنید، ارزش آن را خواهید دید


بیش از حد برای برنامه‌های ساده: برای برخی از برنامه‌ها، ممکن است به Redux، Redux Toolkit یا کتابخانه مدیریت استیت شخص ثالث نیازی نداشته باشید.

ویژگی های کلیدی Redux Toolkit که باید بدانید

Redux Toolkit دارای برخی ویژگی‌های قدرتمند است که استفاده از Redux را برای مدیریت استیت بسیار آسان‌تر از چند سال پیش می‌کند. در اینجا برخی از ویژگی های مهمی است که برای شروع استفاده از RTK باید بدانید.


پیکربندی فروشگاه Redux

فروشگاه Redux شما تنها منبع حقیقت را برای وضعیت برنامه شما نگه می دارد. در گذشته، این Store را با createStore ایجاد می کردید. در حال حاضر، روش توصیه شده استفاده از configureStore است که نه تنها یک فروشگاه برای شما ایجاد می کند، بلکه توابع reducer را نیز می پذیرد.

در اینجا یک مثال اساسی از نحوه عملکرد configureStore آورده شده است:

import { configureStore } from '@reduxjs/toolkit';
import appReducer from 'store/reducers/appSlice';
import cartReducer from 'store/reducers/cartSlice';

const store = configureStore({
  reducer: {
    // Add your reducers here or combine into a rootReducer first
    app: appReducer,
    cart: cartReducer
  },
  // We'll look at adding middleware later
  // middleware: (getDefaultMiddleware) => ...
});

// Use these types for easy typing
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>;

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

ایجاد slices ها با reducers ها و actions

با نگاهی به کد مثال بالا، می بینید که reducer ها از فایل های "slice" وارد می شوند. این فایل ها جایی هستند که بیشتر جادوها اتفاق می افتد. Slice تابعی است که شامل مجموعه‌ای از منطق و اکشن ها reducer Redux و بیشتر بعد از نسخه 2.0 برای یک ویژگی برنامه است.

در اینجا یک slice ساده وجود دارد:

import { createSlice } from '@reduxjs/toolkit';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
  total: number;
}

const initialState: CartState = {
  items: [],
  total: 0,
};

const cartSlice = createSlice({
  // Name the slice
  name: 'cart',
  // Set the initial state
  initialState,
  // Add reducer actions to this object
  reducers: {
    addItem(state, action: { payload: CartItem }) {
      const newItem = action.payload;
      state.items.push(newItem);
      state.total += newItem.price;
    },
    removeItem(state, action: { payload: string }) {
      const itemId = action.payload;
      const itemIndex = state.items.findIndex((item) => item.id === itemId);
      if (itemIndex !== -1) {
        state.items.splice(itemIndex, 1);
        state.total -= state.items[itemIndex].price;
      }
    },
    updateQuantity(state, action: { payload: { itemId: string; newQuantity: number } }) {
      const { itemId, newQuantity } = action.payload;
      const item = state.items.find((item) => item.id === itemId);
      if (item) {
        item.quantity = newQuantity;
        state.total += (newQuantity - item.quantity) * item.price;
      }
    },
  },
});

// Export actions for use in the app
export const { addItem, removeItem, updateQuantity } = cartSlice.actions;
// Export reducer to add to store (the first example)
export default cartSlice.reducer;

اضافه کردن thunks به slice های خود

کد بالا به ما امکان می دهد تا وضعیت Redux را در برنامه به روز کنیم. ما فقط باید اکشن های را که از فایل slice صادر کرده ایم ارسال کنیم. اما اگر بخواهیم استیت را با داده های یک فراخوانی API پر کنیم، چه؟ این نیز بسیار ساده است.


Redux Toolkit با Redux Thunk همراه است. اگر از نسخه 2.0 یا بالاتر استفاده می کنید، می توانید thunks را مستقیماً به slice خود اضافه کنید. یک Thunk در Redux کدهای ناهمزمان را کپسوله می کند. در اینجا نحوه استفاده از آنها در یک slice، با نظراتی که بخش های مهم را توضیح می دهند، آورده شده است:

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  // NOTE: We changed this to a callback to pass create in.
  reducers: (create) => ({
    // NOTE: Standard reducers will now have to use callback syntax.
    // Compare to the last example.
    addItem: create.reducer(state, action: { payload: CartItem }) {
      const newItem = action.payload;
      state.items.push(newItem);
      state.total += newItem.price;
    },
    // To add thunks to your reducer, use create.AsyncThunk instead of create.reducer
    // The first parameter create.AsyncThunk is the actual thunk.
    fetchCartData: create.AsyncThunk<CartItem[], void, {}>(
      'cart/fetchCartData',
       async () => {
        const response = await fetch('https://api.example.com/cart');
        const data = await response.json();
        return data;
      },
      // The second parameter of create.AsyncThunk is an object
      // where you define reducers based on the state of the API call.
    {
      // This runs when the API is first called
       pending: (state) => {
         state.loading = true;
         state.error = null;
       },
      // This runs on an error
       rejected: (state, action) => {
         state.loading = false;
         state.error = true;
       },
      // This runs on success
       fulfilled: (state, action) => {
         state.loading = false;
         state.items = action.payload;
         state.total = calculateTotal(state.items); // Define a helper function
       },
      },
    ),
  }),
});

افزودن selector ها به slice های خود

اغلب اوقات، شما کل شیء استیت را از یک slice نمی خواهید. در واقع، من مطمئن نیستم که چرا شما همه چیز را می خواهید. به همین دلیل است که شما به selector ها نیاز دارید که توابع ساده ای هستند که یک استیت Redux را به عنوان آرگومان می پذیرند و داده هایی را که از آن استیت مشتق شده اند را برمی گرداند.


در Redux Toolkit نسخه 2.0 و جدیدتر، می توانید selector ها را مستقیماً به slice خود اضافه کنید. در اینجا چگونه است:

//...imports, types, and initialState in above code
const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    //... standard reducers in above code
  },
  selectors: {
    selectItems: state => state.items,
    selectTotal: state => state.total,
  },
});

// Exporting selectors
const { selectItems, selectTotal } = cartSlice.selectors;

سپس می توانیم این selector ها را به قسمت های دیگر برنامه وارد کنیم:

import { selectItems, selectTotal } from 'store/reducers/cartSlice';

const itemsInCart = selectItems();

استفاده از میدلور

Redux Toolkit یک تابع getDefaultMiddleware را ارائه می‌کند که آرایه‌ای از میدلور را که توسط configureStore اعمال می‌شود، برمی‌گرداند. این مجموعه پیش‌فرض میدلور شامل:

actionCreatorCheck: اطمینان حاصل می کند که اکشن ها ارسال شده با استفاده از createAction برای سازگاری ایجاد شده اند
immutableCheck: در مورد جهش در شیء استیت هشدار می دهد
thunk: عملیات ناهمزمان و عوارض جانبی را با اجازه دادن به توابع درون اکشن ها برای ارسال کنش های دیگر یا تعامل با API های خارجی فعال می کند.
serializableCheck: در صورت شناسایی مقادیر غیرقابل سریال در وضعیت هشدار می دهد


می‌توانید میدلور را با ارسال یک گزینه میدلور به پیکربندی فروشگاه، با یک تابع callback که میدلور پیش‌فرض را به‌عنوان آرگومان دریافت می‌کند، سفارشی کنید. به عنوان مثال:

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
// The middleware we want to add
import logger from 'redux-logger';

const store = configureStore({
  reducer: rootReducer,
  // Using a callback function to customize the middleware
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      // You can disable or configure the default middleware
      .configure({ serializableCheck: false })
      // You can add more middleware
      .concat(logger),
});

export default store;

در Redux Toolkit نسخه 2.0 و جدیدتر، این میدلور می تواند پویا باشد، به این معنی که می توانید آن را در زمان اجرا اضافه کنید.

در اینجا نحوه انجام این کار آمده است:

import { configureStore, getDefaultMiddleware, createDynamicMiddleware } from '@reduxjs/toolkit';

export const dynamicMiddleware = createDynamicMiddleware();

const store = configureStore({
  reducer: rootReducer,
  // Using a callback function to customize the middleware
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .prepend(dynamicMiddleware.middleware),
});

افزودن کد بالا به فروشگاه، فروشگاه را برای پذیرش میدلور پویا تنظیم می کند. اکنون در بقیه برنامه ما، می‌توانیم میدلور را در زمان اجرا به این صورت اضافه کنیم:

import { dynamicMiddleware } from 'store';
import logger from 'redux-logger';

if (someCondition) {
  dynamicMiddleware.addMiddleware(logger);
}

دیباگ با Redux DevTools

من نمی گویم افزونه Redux DevTools، چه نسخه کروم یا فایرفاکس، برای توسعه با Redux ضروری است. با این حال، بسیار نزدیک است. اشکال زدایی وضعیت Redux برنامه شما با عبارات console.log امکان پذیر است، اما من Redux DevTools را بیش از رویکرد console.log توصیه می کنم.


خبر خوب این است که وقتی از configureStore استفاده می کنید، به طور خودکار Redux DevTools را برای شما راه اندازی می کند. هنگام استفاده از createStore، باید Redux را پیکربندی کنید تا خودتان از این افزونه استفاده کنید.

از مواردی که برای Redux Toolkit استفاده کنید

Redux Toolkit واقعا همه کاره است و می تواند برای طیف گسترده ای از برنامه ها استفاده شود. بیایید در مورد مکان هایی که در آن می درخشد صحبت کنیم.


مدیریت وضعیت برنامه پیچیده

بزرگترین مزیت Redux این است که وضعیت را در برنامه های در حال رشد و پیچیده به طور مداوم مدیریت کنید. Redux Toolkit استفاده از Redux را برای این منظور ساده تر می کند. ایده آل است برای:

 

  • مدیریت داده ها از چندین API

  • ذخیره/ واکشی اولیه داده با RTK Query که همراه با RTK است

  • برقراری ارتباط بین اجزای جدا شده

  • قابلیت لغو/دوباره

  • منطق استیت مشترک در سراسر سیستم عامل ها

  • پیاده سازی جریان های کاری استیت پیشرفته


Redux Toolkit با کمک‌هایی برای گردش‌های کاری پیچیده ارائه می‌شود. ویژگی هایی مانند createAsyncThunk و createEntityAdaptor پیاده سازی را سرعت می بخشد:

 

  • واکشی داده های غیر همگام

  • به روز رسانی بیدرنگ

  • به روز رسانی خوش بینانه رابط کاربری


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


مهاجرت پایگاه های کد Redux موجود

اگر در حال حاضر از Redux استفاده می کنید، زمان آن رسیده است که به Redux Toolkit بروید، زیرا اکنون راه رسمی استفاده از Redux است. فرآیند مهاجرت نسبتاً ساده است. می‌توانید با انتقال تک تک به RTK به استفاده از وانیلی Redux ادامه دهید.


فرآیند ساخت جدید Redux Toolkit

Redux Toolkit در گذشته مشکلاتی داشت که آن را با ویژگی‌های جاوا اسکریپت مدرن ناسازگار می‌کرد، از جمله:

 

  • ناسازگاری ESM: به دلیل عدم وجود فیلد صادراتی در فایل package.json، RTK به طور همزمان در کد کلاینت و سرور به درستی بارگیری نشد.

  • وارد کردن mjs. نادرست: وارد کردن RTK در یک فایلmjs. به دلیل استفاده از ماژول انجام نشد اما فیلد صادراتی وجود نداشت.

  • وضوح ماژول TypeScript node16: RTK با گزینه جدید وضوح ماژول TypeScript کار نمی کند
     

تیم Redux برای رفع این محدودیت ها در نسخه 2.0 اقدام کرد. در اینجا چیزی است که آنها تغییر دادند:
 

  • زمینه صادرات در package.json: ساخت مدرن ESM اکنون مصنوع اصلی است، در حالی که CJS برای سازگاری گنجانده شده است. این مشخص می کند که کدام مصنوعات بارگذاری شوند و استفاده مناسب در محیط های مختلف را تضمین می کند

  • نوسازی خروجی ساخت: دیگر نیازی به ترجمه نیست - RTK اکنون دستور زبان جاوا اسکریپت مدرن ES2020 را هدف قرار می دهد و با استانداردهای جاوا اسکریپت فعلی همسو می شود. علاوه بر این، مصنوعات ساخت تحت ./dist/ ادغام می شوند و ساختار بسته را ساده می کند. پشتیبانی TypeScript نیز افزایش یافته است و حداقل نسخه پشتیبانی شده نسخه 4.7 است

  • حذف ساخت‌های UMD: بیلدهای UMD، که عمدتاً برای واردات مستقیم تگ‌های اسکریپت استفاده می‌شوند، به دلیل موارد استفاده محدود امروز حذف شدند. یک ساخت ESM آماده برای مرورگر dist/$PACKAGE_NAME.browser.mjs - هنوز برای بارگیری برچسب اسکریپت از طریق Unpkg در دسترس است

Redux Toolkit در مقابل مشابه

خوب، قبلاً ذکر کردیم که چرا از Redux Toolkit استفاده می کنید و چرا نمی خواهید استفاده کنید. حالا بیایید به بخش سرگرم کننده بپردازیم: مقایسه Redux Toolkit با کتابخانه های مشابه.

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

MobX منحنی یادگیری آسان‌تر و مزایای بالقوه عملکرد را برای برنامه‌های کاربردی ساده‌تر ارائه می‌کند، اما نیاز به مدیریت دقیق واکنش‌پذیری دارد. Zustand سبک وزن است و برای نیازهای استیت کوچکتر کارآمد است، در حالی که Jotai در حال افزایش است اما اکوسیستم کوچکتری دارد.

نتیجه

Redux Toolkit امروزه راه رسمی و استاندارد برای ساخت برنامه های Redux است. این boilerplate ی را که به طور سنتی Redux را تا حدودی مشکل ساز می کرد، از بین می برد، در حالی که مزایای آن را برای مدیریت استیت حفظ و افزایش می دهد.


جایگزین‌های RTK شامل MobX است که سادگی و مزایای بالقوه عملکرد را ارائه می‌دهد و Zustand که برای نیازهای استیت‌های کوچک‌تر سبک و کارآمد است. Jotai نیز با رویکرد مختصر خود در حال جلب توجه است، اما اکوسیستم کمتر بالغی دارد. و البته، هر اپلیکیشنی به کتابخانه مدیریت استیت نیاز ندارد.


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

#redux#rtk#redux_toolkit#mobx#react#vue#js
نظرات ارزشمند شما :
Loading...