MutableState — الحالة القابلة للملاحظة وآلية التحديث في Compose

المؤلف: IT Sectr نُشر: 2026-06-28 وقت القراءة: 7 دق

MutableState هي واجهة في Jetpack Compose تمثل حاوية لقيمة قابلة للتغيير والملاحظة. وهي أساس النظام التفاعلي في Compose: في كل مرة تتغير قيمة MutableState عبر الضابط (setter)، يقوم Compose Runtime بإخطار جميع المكونات القارئة وتشغيل إعادة التركيب. وفقًا لـ Google Android Developers, 2026، فإن فهم MutableState أمر إلزامي للعمل الصحيح مع الحالة في واجهة المستخدم التصريحية.

الرئيسية

  • MutableState — واجهة compose.runtime بخاصية واحدة value (getter + setter)
  • State — الواجهة الأم للقراءة فقط، MutableState تضيف إمكانية الكتابة
  • إعادة التركيب يتم تشغيلها عند استدعاء setter الخاص بـ value داخل دورة snapshot نشطة
  • SnapshotMutationPolicy يحدد متى يعتبر التغيير مهمًا لإعادة التركيب
  • MutableIntState وما شابه — إصدارات محسنة بدائية من MutableState

ما هو MutableState في Jetpack Compose

MutableState هي واجهة من حزمة androidx.compose.runtime تعلن عن خاصية واحدة: override var value: T. يقوم getter بإرجاع القيمة الحالية، ويقوم setter بكتابة قيمة جديدة وإخطار Compose Runtime بالتغيير. الواجهة ترث من State<T>، حيث value متاحة للقراءة فقط. هذا الهيكل ثنائي المستوى يسمح بفصل الوصول: المكون الذي يحتاج فقط إلى قراءة القيمة يستلم State<T>، بينما المكون المالك يستلم MutableState<T>.

التنفيذ الافتراضي لـ MutableState هو الفئة الداخلية SnapshotMutableStateImpl، التي تستخدم آلية snapshots لتتبع التغييرات. عند استدعاء setter الخاص بـ value، يقوم snapshot الحالي بتسجيل الكتابة ووضع علامة على جميع ObservedScope المسجلة بأنها غير صالحة. هذه النطاقات (عادةً دوال Composable) ستتم إعادة تركيبها في الإطار التالي. العملية بأكملها تحدث بشكل متزامن وبدون أقفال بفضل بنية Lock-free snapshot.

State مقابل MutableState: State هي واجهة للقراءة فقط تُستخدم لواجهات API العامة للمكونات. عندما تعلن عن معامل دالة Composable كـ State<Int>، فإنك تضمن أن المكون يمكنه القراءة ولكن لا يمكنه تغيير الحالة. يتم استخدام MutableState داخل المكون المالك. هذا الفصل هو أحد الممارسات الأساسية في Compose التي تمنع التغييرات غير المصرح بها.

تسلسل State وMutableState والواجهات المشتقة

يتكون تسلسل واجهات الحالة في Compose من عدة مستويات. في القمة State<T> بقيمة للقراءة فقط. تحته MutableState<T> بقيمة للقراءة والكتابة. ثم تأتي الإصدارات البدائية المتخصصة: MutableIntState وMutableFloatState وMutableLongState وMutableBooleanState وغيرها، التي تتجنب التغليف (boxing) للبدائيات.

MutableDoubleState وMutableLongState أقل شيوعًا لكنها موجودة أيضًا. واجهات المجموعات: MutableListState — لتتبع التغييرات داخل القائمة، MutableStateMap — للخرائط. كل واحدة من هذه الواجهات محسنة لسيناريو محدد وتوسع MutableState الأساسية بطرق إضافية للتعامل مع المجموعات.

SnapshotStateList وSnapshotStateMap هما تنفيذان للقوائم والخرائط القابلة للتغيير والمتوافقة مع snapshots. تسمح بتتبع ليس فقط استبدال القيمة، بل التغييرات الداخلية: إضافة عنصر إلى القائمة، حذف، تعديل عنصر موجود. لهذه الهياكل، تقوم mutableStateListOf() و mutableStateMapOf() بإنشاء المجموعات القابلة للملاحظة المقابلة.

الواجهةالغرضطريقة الإنشاء
State<T>حاوية للقراءة فقط
MutableState<T>حاوية للقراءة والكتابةmutableStateOf()
MutableIntStateInt بدائي بدون تغليفmutableIntStateOf()
MutableFloatStateFloat بدائي بدون تغليفmutableFloatStateOf()
SnapshotStateListقائمة قابلة للملاحظةmutableStateListOf()
SnapshotStateMapخريطة قابلة للملاحظةmutableStateMapOf()

SnapshotMutationPolicy: متى يتم تشغيل إعادة التركيب

SnapshotMutationPolicy هي واجهة تحدد متى يعتبر التغيير في MutableState مهمًا. تقبل mutableStateOf السياسة (policy) كوسيط ثانٍ. التطبيقات القياسية: structuralEquality() (equals)، referentialEquality() (===)، neverEqualPolicy() (تعتبر التغيير دائمًا مهمًا). يمكن تنفيذ سياسة مخصصة للمنطق الخاص.

structuralEquality() — السلوك الافتراضي. يقارن Compose القيمة الجديدة مع القديمة عبر equals(). إذا كانت النتيجة true، لا يتم تشغيل إعادة التركيب. هذا مناسب للبدائيات و data classes، حيث يعتبر مثيلان بنفس الحقول متساويين. المشكلة: إذا كانت data class تحتوي على List، فإن equals() يقوم بمقارنة عميقة، مما قد يكون مكلفًا للقوائم الكبيرة.

referentialEquality() — يقارن المراجع عبر ===. يتم تشغيل إعادة التركيب فقط عند تعيين كائن مختلف، حتى لو كان المحتوى متطابقًا. هذا مثالي لـ data classes غير القابلة للتغيير حيث يضمن كل مثيل جديد تغييرًا. neverEqualPolicy() — تعتبر التغيير مهمًا دائمًا دون إجراء مقارنة. مفيد عندما يتم استدعاء setter نادرًا ولا داعي لإضاعة الوقت في equals.

kotlin
    // Policy comparison in practice
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: recomposition ONLY if data changed
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: recomposition on ANY assignment
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() with same fields does NOT trigger recomposition
    // user2: even user2.copy() == user2 triggers recomposition (new ref)
}

State البدائية: MutableIntState وMutableFloatState وMutableLongState

MutableIntState البدائي وما شابهه هي واجهات متخصصة تخزن البدائيات بدون تغليف. MutableState<Int> العادي يخزن Int كـ Integer، مما ينشئ كائنًا على heap مع كل كتابة. MutableIntState يخزن int (بدائي)، مما يلغي تمامًا الحمل الزائد للتغليف. هذا مهم بشكل خاص للتحديثات عالية التردد — العدادات، مواضع التمرير، قيم الرسوم المتحركة.

mutableIntStateOf() و mutableFloatStateOf() و mutableLongStateOf() — دوال تنشئ MutableState بدائية. الواجهات تسمى MutableIntState وMutableFloatState وMutableLongState. توسع MutableState<Int> وMutableState<Float> وMutableState<Long> على التوالي، مضيفة الخاصية intValue للوصول السريع إلى البدائي. يستخدم تنفيذها الداخلي AtomicInteger للقراءة/الكتابة بدون أقفال.

الاستخدام: العدادات (Int)، مواضع التمرير (Float offset)، الطوابع الزمنية (Long). في معظم السيناريوهات اليومية، فرق الأداء غير ملحوظ، لكن في LazyList بآلاف العناصر ورسوم متحركة انتقالية، توفر State البدائية تحسنًا ملحوظًا. توصي Google باستخدام State البدائية للسيناريوهات النموذجية بدلاً من mutableStateOf العام.

kotlin
@Composable
fun ScrollCounter() {
    // Bad: boxing on every update
    var badCount by remember { mutableStateOf(0) }

    // Good: no boxing, primitive storage
    var goodCount by remember { mutableIntStateOf(0) }

    // Usage is identical
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

أمثلة عملية للعمل مع MutableState

لننظر إلى مكون TodoList، حيث يتم استخدام MutableState بشكلين: كمتغيرات منفصلة لحالة الإدخال وكـ SnapshotStateList لقائمة ديناميكية من المهام. كلاهما يستخدم التفويض لاختصار الكود.

kotlin
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)

@Composable
fun TodoScreen() {
    var inputText by remember { mutableStateOf("") }
    val items = remember { mutableStateListOf() }

    Column(modifier = Modifier.padding(16.dp)) {
        Row {
            TextField(
                value = inputText,
                onValueChange = { inputText = it }
            )
            Button(onClick = {
                if (inputText.isNotBlank()) {
                    items.add(TodoItem(items.size, inputText))
                    inputText = ""
                }
            }) { Text("Add") }
        }

        LazyColumn {
            items(items) { item ->
                Row(modifier = Modifier.fillMaxWidth().clickable {
                    val idx = items.indexOf(item)
                    items[idx] = item.copy(isDone = !item.isDone)
                }) {
                    Checkbox(checked = item.isDone, onCheckedChange = null)
                    Text(item.text)
                }
            }
        }
    }
}

mutableStateListOf ينشئ SnapshotStateList — قائمة قابلة للتغيير تتتبع التغييرات في العناصر الفردية. عند استدعاء items.add() و items[n] = newValue، يرى Compase التغيير ويعيد تركيب فقط عناصر LazyColumn التي تغيرت. inputText هو MutableState<String> عادي. الجمع بين نوعين من MutableState (فردي ومجموعة) هو نمط نموذجي للشاشات ذات النماذج والقوائم.

الأسئلة الشائعة

هل يمكن استخدام MutableState بدون remember؟

MutableState بدون remember سيتم إنشاؤه من جديد في كل إعادة تركيب. كل استدعاء جديد لـ mutableStateOf ينشئ كائنًا جديدًا، وتفقد القيمة القديمة. استخدم دائمًا remember للحفاظ على State بين عمليات إعادة التركيب، ما لم يتم إنشاء State خارج Composable (مثل ViewModel).

كيفية تحويل MutableState إلى متغير عادي؟

اقرأ .value مرة واحدة خارج snapshot عبر snapshot { }. لكن هذا يعطل التفاعلية — لن تؤدي التغييرات بعد الآن إلى تشغيل إعادة التركيب. للقراءة لمرة واحدة بدون اشتراك، استخدم currentValue() داخل snapshot بدون قراءة.

أيهما أسرع: mutableStateOf أم mutableIntStateOf؟

mutableIntStateOf أسرع لأنه لا يتطلب تغليف int إلى Integer. مع آلاف التحديثات في الثانية (الرسوم المتحركة، التمرير)، يمكن أن يصل الفرق إلى 30-50% في وقت التخصيص. للتحديثات النادرة (النقرات، إدخال النص)، الفرق ضئيل.

هل يمكن استخدام MutableState كوسيط لدالة Composable؟

ممكن لكن غير موصى به. بدلاً من MutableState، مرر State (للقراءة فقط) + لامدا onValueChange. هذا ينفذ نمط State Hoisting ويجعل المكون قابلًا لإعادة الاستخدام. المكونات التي تقبل MutableState تنتهك تدفق البيانات أحادي الاتجاه.

كيفية إنشاء تنفيذ مخصص لـ MutableState؟

نفذ واجهة MutableState وقدم override var value مع getter و setter. في الـ setter يمكنك إضافة تحقق أو تسجيل. للتوافق مع الإصدارات السابقة مع Compose Runtime، لف تنفيذك المخصص في snapshotFlow أو استخدم snapshotIncrement.

الخلاصة

  • MutableState — الواجهة الأساسية للحالة القابلة للتغيير والملاحظة في Compose
  • State — إصدار للقراءة فقط لتمرير البيانات بدون حق التعديل
  • SnapshotMutationPolicy يدير شروط تشغيل إعادة التركيب عند التغيير
  • State البدائية (MutableIntState وغيرها) تلغي الحمل الزائد للتغليف
  • SnapshotStateList وSnapshotStateMap يتتبعان التغييرات الداخلية للمجموعات
  • إعادة التركيب يتم تشغيلها تلقائيًا عند أي استدعاء لـ setter الخاص بـ value
  • التوصية: استخدم MutableState لامتلاك الحالة وState لتمريرها للأسفل

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا