دليل شامل لإدارة الحالة في React لجمهور عالمي. استكشف useState، وContext API، وuseReducer، ومكتبات شهيرة مثل Redux، وZustand، وTanStack Query.
إتقان إدارة الحالة في React: دليل المطور العالمي
في عالم تطوير الواجهات الأمامية، تعد إدارة الحالة (state) واحدة من أهم التحديات. بالنسبة للمطورين الذين يستخدمون React، تطور هذا التحدي من مجرد اهتمام على مستوى المكون البسيط إلى قرار معماري معقد يمكن أن يحدد قابلية التطبيق للتوسع، وأداءه، وسهولة صيانته. سواء كنت مطورًا منفردًا في سنغافورة، أو جزءًا من فريق موزع عبر أوروبا، أو مؤسس شركة ناشئة في البرازيل، فإن فهم مشهد إدارة الحالة في React أمر ضروري لبناء تطبيقات قوية واحترافية.
سيرشدك هذا الدليل الشامل عبر طيف إدارة الحالة بأكمله في React، بدءًا من أدواتها المدمجة وصولًا إلى المكتبات الخارجية القوية. سنستكشف "لماذا" وراء كل نهج، ونقدم أمثلة عملية للكود، ونوفر إطارًا لاتخاذ القرار لمساعدتك في اختيار الأداة المناسبة لمشروعك، بغض النظر عن مكان وجودك في العالم.
ما هي "الحالة" (State) في React، ولماذا هي بهذه الأهمية؟
قبل أن نتعمق في الأدوات، دعونا نؤسس فهمًا واضحًا وعالميًا لـ "الحالة". في جوهرها، الحالة هي أي بيانات تصف وضع تطبيقك في نقطة زمنية محددة. يمكن أن تكون أي شيء:
- هل المستخدم مسجل دخوله حاليًا؟
- ما هو النص الموجود في حقل إدخال بالنموذج؟
- هل النافذة المنبثقة (modal) مفتوحة أم مغلقة؟
- ما هي قائمة المنتجات في عربة التسوق؟
- هل يتم جلب البيانات حاليًا من الخادم؟
بُنيت React على مبدأ أن واجهة المستخدم هي دالة للحالة (UI = f(state)). عندما تتغير الحالة، تقوم React بكفاءة بإعادة تصيير (re-render) الأجزاء الضرورية من واجهة المستخدم لتعكس هذا التغيير. يظهر التحدي عندما تحتاج هذه الحالة إلى المشاركة والتعديل من قبل مكونات متعددة غير مرتبطة مباشرة في شجرة المكونات. هنا تصبح إدارة الحالة مصدر قلق معماري حاسم.
الأساس: الحالة المحلية (Local State) مع useState
تبدأ رحلة كل مطور React مع الخطاف useState
. إنها أبسط طريقة للإعلان عن جزء من الحالة يكون محليًا لمكون واحد.
على سبيل المثال، إدارة حالة عداد بسيط:
import React, { useState } from 'react';
function Counter() {
// 'count' هو متغير الحالة
// 'setCount' هي الدالة لتحديثه
const [count, setCount] = useState(0);
return (
لقد نقرت {count} مرات
);
}
يعتبر useState
مثاليًا للحالة التي لا تحتاج إلى مشاركة، مثل حقول الإدخال في النماذج، أو مفاتيح التبديل (toggles)، أو أي عنصر واجهة مستخدم لا يؤثر وضعه على أجزاء أخرى من التطبيق. تبدأ المشكلة عندما تحتاج إلى مكون آخر لمعرفة قيمة `count`.
النهج الكلاسيكي: رفع الحالة للأعلى (Lifting State Up) وتمرير الخصائص (Prop Drilling)
الطريقة التقليدية في React لمشاركة الحالة بين المكونات هي "رفعها للأعلى" إلى أقرب سلف مشترك بينها. ثم تتدفق الحالة لأسفل إلى المكونات الأبناء عبر الخصائص (props). هذا نمط أساسي ومهم في React.
ومع ذلك، مع نمو التطبيقات، يمكن أن يؤدي هذا إلى مشكلة تُعرف باسم "تمرير الخصائص" (prop drilling). يحدث هذا عندما تضطر إلى تمرير الخصائص عبر طبقات متعددة من المكونات الوسيطة التي لا تحتاج إلى البيانات نفسها، فقط لإيصالها إلى مكون ابن متداخل بعمق يحتاجها. هذا يمكن أن يجعل قراءة الكود وإعادة هيكلته وصيانته أكثر صعوبة.
تخيل تفضيل السمة (theme) للمستخدم (مثل 'dark' أو 'light') الذي يحتاج إلى الوصول إليه بواسطة زر في عمق شجرة المكونات. قد تضطر إلى تمريره هكذا: App -> Layout -> Page -> Header -> ThemeToggleButton
. فقط `App` (حيث يتم تعريف الحالة) و`ThemeToggleButton` (حيث يتم استخدامها) يهتمان بهذه الخاصية، لكن `Layout` و`Page` و`Header` مجبرون على العمل كوسطاء. هذه هي المشكلة التي تهدف حلول إدارة الحالة الأكثر تقدمًا إلى حلها.
حلول React المدمجة: قوة السياق (Context) والمختزلات (Reducers)
إدراكًا لتحدي تمرير الخصائص، قدم فريق React واجهة برمجة تطبيقات السياق (Context API) والخطاف `useReducer`. هذه أدوات قوية ومدمجة يمكنها التعامل مع عدد كبير من سيناريوهات إدارة الحالة دون إضافة تبعيات خارجية.
1. واجهة برمجة تطبيقات السياق (Context API): بث الحالة على مستوى عالمي
توفر واجهة برمجة تطبيقات السياق طريقة لتمرير البيانات عبر شجرة المكونات دون الحاجة إلى تمرير الخصائص يدويًا في كل مستوى. فكر فيها كمخزن بيانات عالمي لجزء معين من تطبيقك.
استخدام السياق يتضمن ثلاث خطوات رئيسية:
- إنشاء السياق: استخدم `React.createContext()` لإنشاء كائن سياق.
- توفير السياق: استخدم المكون `Context.Provider` لتغليف جزء من شجرة المكونات الخاصة بك وتمرير `value` إليه. يمكن لأي مكون داخل هذا الموفر الوصول إلى القيمة.
- استهلاك السياق: استخدم الخطاف `useContext` داخل مكون للاشتراك في السياق والحصول على قيمته الحالية.
مثال: مبدل سمة (theme) بسيط باستخدام Context
// 1. إنشاء السياق (على سبيل المثال، في ملف theme-context.js)
import { createContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
// كائن القيمة سيكون متاحًا لجميع المكونات المستهلكة
const value = { theme, toggleTheme };
return (
{children}
);
}
// 2. توفير السياق (على سبيل المثال، في ملف App.js الرئيسي)
import { ThemeProvider } from './theme-context';
import MyPage from './MyPage';
function App() {
return (
);
}
// 3. استهلاك السياق (على سبيل المثال، في مكون متداخل بعمق)
import { useContext } from 'react';
import { ThemeContext } from './theme-context';
function ThemeToggleButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
);
}
إيجابيات Context API:
- مدمجة: لا حاجة لمكتبات خارجية.
- البساطة: سهلة الفهم للحالة العامة البسيطة.
- تحل مشكلة تمرير الخصائص: غرضها الأساسي هو تجنب تمرير الخصائص عبر طبقات عديدة.
السلبيات واعتبارات الأداء:
- الأداء: عندما تتغير القيمة في الموفر (provider)، سيتم إعادة تصيير جميع المكونات التي تستهلك هذا السياق. يمكن أن يكون هذا مشكلة أداء إذا كانت قيمة السياق تتغير بشكل متكرر أو كانت المكونات المستهلكة مكلفة في التصيير.
- ليست للتحديثات عالية التردد: هي الأنسب للتحديثات منخفضة التردد، مثل السمة (theme)، أو مصادقة المستخدم، أو تفضيل اللغة.
2. الخطاف useReducer
: لانتقالات الحالة القابلة للتنبؤ
بينما يعتبر useState
رائعًا للحالة البسيطة، فإن useReducer
هو شقيقه الأقوى، المصمم لإدارة منطق الحالة الأكثر تعقيدًا. إنه مفيد بشكل خاص عندما يكون لديك حالة تتضمن قيمًا فرعية متعددة أو عندما تعتمد الحالة التالية على الحالة السابقة.
مستوحى من Redux، يتضمن `useReducer` دالة `reducer` ودالة `dispatch`:
- دالة المختزل (Reducer): دالة نقية (pure function) تأخذ `state` الحالية وكائن `action` كوسائط، وتعيد الحالة الجديدة. `(state, action) => newState`.
- دالة الإرسال (Dispatch): دالة تستدعيها مع كائن `action` لتشغيل تحديث للحالة.
مثال: عداد مع إجراءات الزيادة والنقصان وإعادة التعيين
import React, { useReducer } from 'react';
// 1. تعريف الحالة الأولية
const initialState = { count: 0 };
// 2. إنشاء دالة المختزل (reducer)
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error('Unexpected action type');
}
}
function ReducerCounter() {
// 3. تهيئة useReducer
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
العدد: {state.count}
{/* 4. إرسال الإجراءات عند تفاعل المستخدم */}
>
);
}
استخدام `useReducer` يركز منطق تحديث حالتك في مكان واحد (دالة المختزل)، مما يجعله أكثر قابلية للتنبؤ، وأسهل في الاختبار، وأكثر قابلية للصيانة، خاصة مع نمو المنطق في التعقيد.
الثنائي القوي: useContext
+ useReducer
تتحقق القوة الحقيقية لخطافات React المدمجة عند الجمع بين `useContext` و `useReducer`. يسمح لك هذا النمط بإنشاء حل قوي لإدارة الحالة يشبه Redux دون أي تبعيات خارجية.
- `useReducer` يدير منطق الحالة المعقد.
- `useContext` يبث `state` ودالة `dispatch` إلى أي مكون يحتاجها.
هذا النمط رائع لأن دالة `dispatch` نفسها لها هوية مستقرة ولن تتغير بين عمليات إعادة التصيير. هذا يعني أن المكونات التي تحتاج فقط إلى `dispatch` الإجراءات لن تعيد التصيير بشكل غير ضروري عند تغيير قيمة الحالة، مما يوفر تحسينًا مدمجًا للأداء.
مثال: إدارة عربة تسوق بسيطة
// 1. الإعداد في ملف cart-context.js
import { createContext, useReducer, useContext } from 'react';
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
// منطق لإضافة عنصر
return [...state, action.payload];
case 'REMOVE_ITEM':
// منطق لإزالة عنصر حسب المعرف (id)
return state.filter(item => item.id !== action.payload.id);
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, []);
return (
{children}
);
};
// خطافات مخصصة لسهولة الاستهلاك
export const useCart = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
// 2. الاستخدام في المكونات
// ProductComponent.js - يحتاج فقط إلى إرسال إجراء
function ProductComponent({ product }) {
const dispatch = useCartDispatch();
const handleAddToCart = () => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
return ;
}
// CartDisplayComponent.js - يحتاج فقط إلى قراءة الحالة
function CartDisplayComponent() {
const cartItems = useCart();
return عناصر السلة: {cartItems.length};
}
من خلال تقسيم الحالة والإرسال إلى سياقين منفصلين، نكتسب ميزة في الأداء: المكونات مثل `ProductComponent` التي ترسل الإجراءات فقط لن تعيد التصيير عندما تتغير حالة السلة.
متى يجب اللجوء إلى المكتبات الخارجية؟
نمط `useContext` + `useReducer` قوي، لكنه ليس الحل السحري. مع توسع التطبيقات، قد تواجه احتياجات تخدمها بشكل أفضل المكتبات الخارجية المخصصة. يجب أن تفكر في مكتبة خارجية عندما:
- تحتاج إلى نظام بيئي متطور للبرمجيات الوسيطة (middleware): لمهام مثل التسجيل (logging)، أو استدعاءات API غير المتزامنة (thunks، sagas)، أو التكامل مع التحليلات.
- تتطلب تحسينات أداء متقدمة: مكتبات مثل Redux أو Jotai لديها نماذج اشتراك محسنة للغاية تمنع إعادة التصيير غير الضرورية بفعالية أكبر من إعداد Context الأساسي.
- تصحيح الأخطاء عبر الزمن (Time-travel debugging) يمثل أولوية: أدوات مثل Redux DevTools قوية بشكل لا يصدق لفحص تغييرات الحالة بمرور الوقت.
- تحتاج إلى إدارة حالة الخادم (التخزين المؤقت، المزامنة): مكتبات مثل TanStack Query مصممة خصيصًا لهذا الغرض وهي أفضل بكثير من الحلول اليدوية.
- حالتك العامة كبيرة ويتم تحديثها بشكل متكرر: يمكن أن يسبب سياق واحد كبير اختناقات في الأداء. مديرو الحالة الذرية (Atomic state managers) يتعاملون مع هذا بشكل أفضل.
جولة عالمية في مكتبات إدارة الحالة الشهيرة
النظام البيئي لـ React نابض بالحياة، ويقدم مجموعة واسعة من حلول إدارة الحالة، لكل منها فلسفتها ومقايضاتها الخاصة. دعنا نستكشف بعض الخيارات الأكثر شيوعًا للمطورين حول العالم.
1. Redux (و Redux Toolkit): المعيار الراسخ
لقد كانت Redux المكتبة المهيمنة لإدارة الحالة لسنوات. تفرض تدفق بيانات صارمًا أحادي الاتجاه، مما يجعل تغييرات الحالة قابلة للتنبؤ والتتبع. في حين أن Redux في بداياته كان معروفًا بكثرة الكود التكراري (boilerplate)، فإن النهج الحديث باستخدام Redux Toolkit (RTK) قد بسّط العملية بشكل كبير.
- المفاهيم الأساسية: `store` عالمي واحد يحتوي على كل حالة التطبيق. المكونات ترسل (`dispatch`) `actions` لوصف ما حدث. `Reducers` هي دوال نقية تأخذ الحالة الحالية والإجراء لإنتاج الحالة الجديدة.
- لماذا Redux Toolkit (RTK)؟ RTK هي الطريقة الرسمية والموصى بها لكتابة منطق Redux. إنها تبسط إعداد المخزن، وتقلل من الكود التكراري بواجهة `createSlice` API، وتتضمن أدوات قوية مثل Immer للتحديثات غير القابلة للتغيير بسهولة و Redux Thunk للمنطق غير المتزامن بشكل افتراضي.
- نقطة القوة الرئيسية: نظامها البيئي الناضج لا مثيل له. إضافة متصفح Redux DevTools هي أداة تصحيح أخطاء عالمية المستوى، وبنيتها للبرمجيات الوسيطة قوية بشكل لا يصدق للتعامل مع الآثار الجانبية المعقدة.
- متى تستخدمها: للتطبيقات واسعة النطاق ذات الحالة العالمية المعقدة والمترابطة حيث تكون القابلية للتنبؤ والتتبع وتجربة تصحيح الأخطاء القوية ذات أهمية قصوى.
2. Zustand: الخيار البسيط وغير المقيد
Zustand، التي تعني "الحالة" بالألمانية، تقدم نهجًا بسيطًا ومرنًا. غالبًا ما يُنظر إليها على أنها بديل أبسط لـ Redux، حيث توفر فوائد المخزن المركزي دون الكود التكراري.
- المفاهيم الأساسية: تقوم بإنشاء `store` كخطاف بسيط. يمكن للمكونات الاشتراك في أجزاء من الحالة، ويتم تشغيل التحديثات عن طريق استدعاء الدوال التي تعدل الحالة.
- نقطة القوة الرئيسية: البساطة والحد الأدنى من واجهة برمجة التطبيقات (API). من السهل جدًا البدء بها وتتطلب القليل جدًا من الكود لإدارة الحالة العامة. لا تغلف تطبيقك بموفر، مما يسهل دمجها في أي مكان.
- متى تستخدمها: للتطبيقات الصغيرة إلى المتوسطة الحجم، أو حتى الأكبر حجمًا حيث تريد مخزنًا مركزيًا بسيطًا دون الهيكل الصارم والكود التكراري لـ Redux.
// store.js
import { create } from 'zustand';
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
// MyComponent.js
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return يوجد {bears} دب هنا ...
;
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation);
return ;
}
3. Jotai و Recoil: النهج الذري (Atomic)
Jotai و Recoil (من فيسبوك) تروجان لمفهوم إدارة الحالة "الذرية". بدلاً من كائن حالة واحد كبير، تقوم بتقسيم حالتك إلى أجزاء صغيرة ومستقلة تسمى "الذرات" (atoms).
- المفاهيم الأساسية: `atom` يمثل جزءًا من الحالة. يمكن للمكونات الاشتراك في ذرات فردية. عندما تتغير قيمة ذرة ما، فإن المكونات التي تستخدم تلك الذرة المحددة فقط هي التي ستعيد التصيير.
- نقطة القوة الرئيسية: هذا النهج يحل بشكل جراحي مشكلة الأداء في Context API. يوفر نموذجًا عقليًا يشبه React (مشابه لـ `useState` ولكنه عالمي) ويقدم أداءً ممتازًا بشكل افتراضي، حيث يتم تحسين عمليات إعادة التصيير بشكل كبير.
- متى تستخدمها: في التطبيقات التي تحتوي على الكثير من أجزاء الحالة العالمية الديناميكية والمستقلة. إنه بديل رائع لـ Context عندما تجد أن تحديثات السياق تسبب الكثير من عمليات إعادة التصيير.
4. TanStack Query (سابقًا React Query): ملك حالة الخادم
ربما يكون التحول النموذجي الأكثر أهمية في السنوات الأخيرة هو إدراك أن الكثير مما نسميه "الحالة" هو في الواقع حالة الخادم - بيانات تعيش على خادم ويتم جلبها وتخزينها مؤقتًا ومزامنتها في تطبيق العميل لدينا. TanStack Query ليست مدير حالة عام؛ إنها أداة متخصصة لإدارة حالة الخادم، وتقوم بذلك بشكل جيد للغاية.
- المفاهيم الأساسية: توفر خطافات مثل `useQuery` لجلب البيانات و `useMutation` لإنشاء/تحديث/حذف البيانات. تتعامل مع التخزين المؤقت، وإعادة الجلب في الخلفية، ومنطق البيانات القديمة أثناء التحقق (stale-while-revalidate)، وتقسيم الصفحات، وأكثر من ذلك بكثير، كل ذلك بشكل افتراضي.
- نقطة القوة الرئيسية: تبسط بشكل كبير جلب البيانات وتلغي الحاجة إلى تخزين بيانات الخادم في مدير حالة عالمي مثل Redux أو Zustand. يمكن أن يزيل هذا جزءًا كبيرًا من كود إدارة الحالة من جانب العميل.
- متى تستخدمها: في أي تطبيق تقريبًا يتصل بواجهة برمجة تطبيقات بعيدة (API). يعتبرها العديد من المطورين على مستوى العالم الآن جزءًا أساسيًا من حزمة أدواتهم. غالبًا ما يكون الجمع بين TanStack Query (لحالة الخادم) و`useState`/`useContext` (لحالة واجهة المستخدم البسيطة) هو كل ما يحتاجه التطبيق.
اتخاذ القرار الصحيح: إطار عمل لاتخاذ القرار
قد يبدو اختيار حل لإدارة الحالة أمرًا مربكًا. إليك إطار عمل عملي وقابل للتطبيق عالميًا لتوجيه اختيارك. اسأل نفسك هذه الأسئلة بالترتيب:
-
هل الحالة عالمية حقًا، أم يمكن أن تكون محلية؟
ابدأ دائمًا بـuseState
. لا تقدم حالة عالمية إلا عند الضرورة القصوى. -
هل البيانات التي تديرها هي في الواقع حالة الخادم؟
إذا كانت بيانات من واجهة برمجة تطبيقات، فاستخدم TanStack Query. سيتولى هذا التخزين المؤقت والجلب والمزامنة نيابة عنك. من المحتمل أن يدير 80٪ من "حالة" تطبيقك. -
بالنسبة لحالة واجهة المستخدم المتبقية، هل تحتاج فقط إلى تجنب تمرير الخصائص؟
إذا كانت الحالة يتم تحديثها بشكل غير متكرر (مثل السمة، معلومات المستخدم، اللغة)، فإن Context API المدمج هو حل مثالي وخالٍ من التبعيات. -
هل منطق حالة واجهة المستخدم لديك معقد، مع انتقالات يمكن التنبؤ بها؟
اجمع بينuseReducer
و Context. يمنحك هذا طريقة قوية ومنظمة لإدارة منطق الحالة بدون مكتبات خارجية. -
هل تواجه مشكلات في الأداء مع Context، أو هل حالتك تتكون من العديد من القطع المستقلة؟
فكر في مدير حالة ذري مثل Jotai. إنه يوفر واجهة برمجة تطبيقات بسيطة مع أداء ممتاز عن طريق منع عمليات إعادة التصيير غير الضرورية. -
هل تقوم ببناء تطبيق مؤسسي واسع النطاق يتطلب بنية صارمة يمكن التنبؤ بها، وبرمجيات وسيطة، وأدوات تصحيح أخطاء قوية؟
هذه هي حالة الاستخدام الرئيسية لـ Redux Toolkit. تم تصميم هيكله ونظامه البيئي للتعقيد والصيانة طويلة الأجل في الفرق الكبيرة.
جدول مقارنة موجز
الحل | الأفضل لـ | الميزة الرئيسية | منحنى التعلم |
---|---|---|---|
useState | حالة المكون المحلية | بسيط، مدمج | منخفض جدًا |
Context API | الحالة العامة منخفضة التردد (السمة، المصادقة) | يحل مشكلة تمرير الخصائص، مدمج | منخفض |
useReducer + Context | حالة واجهة المستخدم المعقدة بدون مكتبات خارجية | منطق منظم، مدمج | متوسط |
TanStack Query | حالة الخادم (تخزين/مزامنة بيانات API) | يزيل كميات هائلة من منطق الحالة | متوسط |
Zustand / Jotai | الحالة العامة البسيطة، تحسين الأداء | الحد الأدنى من الكود التكراري، أداء رائع | منخفض |
Redux Toolkit | التطبيقات واسعة النطاق ذات الحالة المشتركة المعقدة | القابلية للتنبؤ، أدوات تطوير قوية، نظام بيئي | مرتفع |
الخاتمة: منظور عملي وعالمي
لم يعد عالم إدارة الحالة في React معركة بين مكتبة وأخرى. لقد نضج ليصبح مشهدًا متطورًا حيث تم تصميم أدوات مختلفة لحل مشكلات مختلفة. النهج الحديث والعملي هو فهم المقايضات وبناء "مجموعة أدوات لإدارة الحالة" لتطبيقك.
بالنسبة لمعظم المشاريع في جميع أنحاء العالم، تبدأ حزمة الأدوات القوية والفعالة بما يلي:
- TanStack Query لجميع حالات الخادم.
useState
لجميع حالات واجهة المستخدم البسيطة وغير المشتركة.useContext
لحالة واجهة المستخدم العالمية البسيطة ومنخفضة التردد.
فقط عندما تكون هذه الأدوات غير كافية، يجب أن تلجأ إلى مكتبة حالة عالمية مخصصة مثل Jotai أو Zustand أو Redux Toolkit. من خلال التمييز بوضوح بين حالة الخادم وحالة العميل، والبدء بأبسط الحلول أولاً، يمكنك بناء تطبيقات عالية الأداء وقابلة للتطوير وممتعة في صيانتها، بغض النظر عن حجم فريقك أو موقع المستخدمين.