تدفق البيانات أحادي الاتجاه — ما هو، UDF في Android وiOS

المؤلف: IT Sectr نُشر: 2026-02-20 وقت القراءة: 13 دق

افهم ما هو تدفق البيانات أحادي الاتجاه — تدفق البيانات في اتجاه واحد، نمط معماري حيث تتحرك البيانات في حلقة مغلقة State → View → Intent → Reducer → State بدون حلقات ردود فعل. على عكس الربط ثنائي الاتجاه، يضمن UDF أن تغييرات الحالة تحدث فقط من خلال إجراءات صريحة (Intent/Event)، مما يجعل تدفق البيانات قابلاً للتنبؤ والتتبع. وفقًا لـ Google I/O 2024، فإن UDF هي البنية الموصى بها لتطبيقات Jetpack Compose وSwiftUI ذات منطق الأعمال متوسط وعالي التعقيد.

النقاط الرئيسية

  • تدفق البيانات أحادي الاتجاه (UDF) — نمط معماري حيث تتغير الحالة بدقة وفقًا للدورة: إدخال المستخدم → Intent → Reducer → State جديد → إعادة عرض View.
  • في Android، يتم تنفيذ UDF عبر ViewModel + StateFlow + معالجة Intent؛ في iOS — عبر @Observable + نمط Reducer (Composable Architecture).
  • توصي Google باستخدام UDF كبنية رئيسية لـ Jetpack Compose، بدءًا من وثائق 2023.
  • يقضي UDF على مشكلة الحلقات اللانهائية للربط ثنائي الاتجاه من خلال مصدر واحد للحقيقة (Single Source of Truth).
  • العيب الرئيسي هو المزيد من الكود النمطي مقارنة بالربط ثنائي الاتجاه (State، Intent، Reducer، Effect).

ما هو تدفق البيانات أحادي الاتجاه؟

تدفق البيانات أحادي الاتجاه (UDF) هو نمط معماري حيث تتحرك البيانات في اتجاه واحد في حلقة مغلقة، مما يلغي حلقات ردود الفعل بين View وModel. على عكس الربط ثنائي الاتجاه، حيث يؤدي التغيير في واجهة المستخدم إلى تحديث النموذج فورًا، يتطلب UDF إجراءً صريحًا (Intent، Event، Action) لكل تغيير في الحالة. وهذا يجعل تدفق البيانات قابلاً للتنبؤ تمامًا: في أي لحظة، يمكن تحديد الإجراء الذي أدى إلى الحالة الحالية.

جاء مفهوم UDF من أطر الويب — Redux (JavaScript، 2015) وElm (2012) — وتم تكييفه لتطوير الأجهزة المحمولة. وفقًا لـ Google I/O 2024، أصبح UDF البنية الموصى بها لـ Jetpack Compose، محلًا MVVM الكلاسيكي مع LiveData. على iOS، يتم تنفيذ نهج مماثل في The Composable Architecture (TCA) من Point-Free، والذي يستخدمه أكثر من 15% من مطوري iOS وفقًا لاستطلاع مجتمع Swift (2024).

الميزة الرئيسية لـ UDF هي مصدر واحد للحقيقة (SSOT): يتم تخزين جميع حالة التطبيق في مكان واحد ويتم تعديلها من خلال عمليات محددة بدقة. هذا يبسط التصحيح والاختبار وإعادة إنتاج الأخطاء، حيث يتم تسجيل كل تغيير في الحالة ويمكن إعادة إنتاجه عن طريق إعادة إرسال نفس Intents.

كيف يعمل UDF: دورة State → View → Intent → Reducer

تتكون دورة UDF الأساسية من أربع خطوات: يتم عرض State (الحالة الحالية) في View؛ يقوم المستخدم بإجراء يتحول إلى Intent؛ تتم معالجة Intent في Reducer (دالة نقية)، والتي تنشئ State جديدًا؛ يتم تمرير الحالة الجديدة إلى View لإعادة العرض. تتكرر هذه الدورة في كل حدث مستخدم أو حدث نظام.

لكل عنصر في الدورة مسؤولية صارمة: State — كائن غير قابل للتغيير يصف حالة الشاشة في لحظة معينة؛ View — دالة تعرض State؛ Intent — قيمة تصف نية المستخدم (مثل LoginIntent.Submit)؛ Reducer — دالة نقية بدون آثار جانبية، تأخذ State الحالي وIntent وتعيد State جديدًا. يتم نقل الآثار الجانبية (طلبات الشبكة، عمليات قاعدة البيانات) إلى طبقة Middleware أو Effect منفصلة.

وفقًا لمقال Google Android Architecture (2024)، نقاء Reducer هو مطلب رئيسي: إذا كان Reducer يحتوي على استدعاء شبكة أو كتابة في قاعدة البيانات، يصبح اختبار وتصحيح تدفق البيانات مستحيلًا. يجب تنفيذ جميع الآثار الجانبية في كوروتين ViewModel أو Swift Task قبل استدعاء Reducer، ويجب إرسال النتيجة كـ Intent جديد.

UDF في Android: ViewModel + StateFlow + Intent

في Android، يعتمد تنفيذ UDF على ثلاثة مكونات من Jetpack: ViewModel يدير دورة الحياة، StateFlow يوفر تدفق حالة تفاعلي، Intent (sealed class) يصف جميع الإجراءات الممكنة للمستخدم. تشترك View في StateFlow عبر collectAsState() في Compose أو observe() في نظام View.

Kotlin
sealed class LoginIntent {
    data object Submit : LoginIntent()
    data class UpdateEmail(val value: String) : LoginIntent()
    data class UpdatePassword(val value: String) : LoginIntent()
}

data class LoginState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

class LoginViewModel : ViewModel() {
    private val _state = MutableStateFlow(LoginState())
    val state: StateFlow<LoginState> = _state.asStateFlow()

    fun onIntent(intent: LoginIntent) {
        when (intent) {
            is LoginIntent.UpdateEmail -> {
                _state.update { it.copy(email = intent.value) }
            }
            is LoginIntent.UpdatePassword -> {
                _state.update { it.copy(password = intent.value) }
            }
            is LoginIntent.Submit -> {
                _state.update { it.copy(isLoading = true, error = null) }
                loginUseCase(_state.value.email, _state.value.password)
                    .onSuccess {
                        _state.update { it.copy(isLoading = false) }
                    }
                    .onFailure { e ->
                        _state.update { it.copy(isLoading = false, error = e.message) }
                    }
            }
        }
    }
}

@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val state by viewModel.state.collectAsState()

    LoginForm(
        email = state.email,
        onEmailChange = { viewModel.onIntent(LoginIntent.UpdateEmail(it)) },
        password = state.password,
        onPasswordChange = { viewModel.onIntent(LoginIntent.UpdatePassword(it)) },
        isLoading = state.isLoading,
        onSubmit = { viewModel.onIntent(LoginIntent.Submit) }
    )
}

يوضح المثال دورة UDF الكاملة في Android: LoginIntent يصف جميع الإجراءات الممكنة (تغيير البريد الإلكتروني، تغيير كلمة المرور، إرسال النموذج)، LoginState هو الحالة غير القابلة للتغيير، LoginViewModel يعالج Intents ويحدث StateFlow، وتشترك شاشة Compose في الحالة عبر collectAsState(). كل تغيير في الحالة هو نتيجة معالجة Intent محدد، مما يجعل تدفق البيانات شفافًا تمامًا.

UDF في iOS: TCA ونمط Observable

في iOS، يتم تنفيذ UDF من خلال The Composable Architecture (TCA) من Point-Free أو نمط Observable الأصلي مع iOS 17+. يوفر TCA دورة جاهزة من State + Action + Reducer + Store، حيث Store هو المصدر الوحيد للحقيقة، وتشترك View في التغييرات عبر @Observable أو ObservableObject.

Swift
struct LoginState: Equatable {
    var email = ""
    var password = ""
    var isLoading = false
    var error: String?
}

enum LoginAction {
    case emailChanged(String)
    case passwordChanged(String)
    case submit
    case loginResponse(Result<User, Error>)
}

let loginReducer = Reducer<LoginState, LoginAction> { state, action in
    switch action {
    case .emailChanged(let email):
        state.email = email
        return .none
    case .passwordChanged(let password):
        state.password = password
        return .none
    case .submit:
        state.isLoading = true
        state.error = nil
        return .run { send in
            let result = await loginUseCase(state.email, state.password)
            await send(.loginResponse(result))
        }
    case .loginResponse(.success):
        state.isLoading = false
        return .none
    case .loginResponse(.failure(let error)):
        state.isLoading = false
        state.error = error.localizedDescription
        return .none
    }
}

struct LoginView: View {
    let store: StoreOf<LoginReducer>

    var body: some View {
        WithViewStore(store, observe: { $0 }) { viewStore in
            Form {
                TextField("Email", text: viewStore.binding(get: \.email, send: { .emailChanged($0) }))
                SecureField("Password", text: viewStore.binding(get: \.password, send: { .passwordChanged($0) }))
                Button("تسجيل الدخول") { viewStore.send(.submit) }
            }
        }
    }
}

loginReducer هي دالة نقية: لا تقوم بطلبات الشبكة مباشرة، بل تعيد Effect سيتم تنفيذه بواسطة بيئة TCA. هذا يسمح باختبار المخفض بشكل معزول عن طريق استبدال التأثيرات في الاختبارات. تشترك View في تغييرات Store عبر WithViewStore وترسل Actions عبر send(). تتعامل TCA تلقائيًا مع إلغاء التأثيرات عند تدمير Store، مما يمنع تسرب الذاكرة.

UDF مقابل MVVM: ما الفرق؟

غالبًا ما يتم الخلط بين MVVM وUDF، ولكن هناك فرق جوهري بينهما. MVVM هو نمط هيكلي يفصل الكود إلى ثلاث طبقات (Model، View، ViewModel) لكنه لا يحدد اتجاه تدفق البيانات. UDF هو نمط سلوكي يصف كيفية تحرك البيانات داخل هذا الهيكل. في MVVM مع LiveData، كل من الربط ثنائي الاتجاه والتدفق أحادي الاتجاه ممكنان — يضيف UDF قواعد صارمة لمعالجة Intent إلى MVVM.

وفقًا لوثائق Android Developers (2024)، البنية الموصى بها لـ Compose هي UDF داخل MVVM: ViewModel يخزن State ويعالج Intents، View تشترك في State وترسل Intents. توصي Google باستخدام MVVM الكلاسيكي مع الربط ثنائي الاتجاه عبر DataBinding فقط للشاشات البسيطة بدون منطق أعمال. بالنسبة لـ Jetpack Compose، السيناريو الرئيسي هو UDF مع معالجة صريحة للأحداث.

جدول المقارنة:

الخاصيةMVVM (كلاسيكي)MVVM + UDF
تدفق البياناتغير محددأحادي الاتجاه بدقة
تغيير الحالةمباشرة عبر setText()فقط من خلال Intent → Reducer
مصدر واحد للحقيقةلانعم
قابلية اختبار Reducerمنخفضةعالية (دالة نقية)
توصية Googleنهج قديمرئيسي لـ Compose

الأخطاء الشائعة عند تنفيذ UDF

الخطأ الأكثر شيوعًا هو الآثار الجانبية داخل Reducer. المطورون المعتادون على MVVM يضعون طلبات الشبكة مباشرة في معالج Intent، مما يجعل Reducer غير نقي ويكسر قابلية الاختبار. يجب إعادة جميع التأثيرات كقيمة (Effect / SideEffect) وتنفيذها بواسطة البنية التحتية للإطار. في Android، تُستخدم الكوروتينات في ViewModel لهذا؛ في TCA — Effect.run.

الخطأ الثاني هو Intents مفصلة بشكل مفرط. كل ضغطة مفتاح، وحركة شريط تمرير، وتغيير نص يولد Intent منفصل. لحقول الإدخال، هذا مفرط — في مثل هذه الحالات، من المقبول استخدام Binding مع تدفق أحادي الاتجاه داخل النموذج (حالة محلية)، وإرسال Intent عالمي فقط للإجراءات الهامة (إرسال، تنقل).

الخطأ الثالث هو عدم وجود معالجة لإلغاء التأثيرات. إذا غادر المستخدم الشاشة بينما لا يزال الكوروتين أو Task قيد التنفيذ، فقد يتم تطبيق النتيجة على View مدمرة بالفعل. في Android، استخدم viewModelScope.cancel() أو takeWhileActive()؛ في TCA، يتم إلغاء التأثيرات تلقائيًا عند تدمير Store. وفقًا لـ Google Issue Tracker (2024)، تسرب الكوروتينات غير المكتملة هو من بين الأسباب الخمسة الأولى للأعطال في تطبيقات Compose.

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

كيف يختلف UDF عن MVI؟

MVI (Model-View-Intent) هو حالة محددة من UDF مع ثلاثة عناصر إلزامية: Intent (نية)، Model (حالة)، View (عرض). الفرق الرئيسي هو أنه في MVI، يتم وصف كل حالة شاشة بهيكل واحد غير قابل للتغيير (Sealed class)، وView هي دالة نقية من Model إلى UI. UDF هو مصطلح أوسع يصف أي تدفق أحادي الاتجاه، بما في ذلك Redux وElm. في وثائق Google، يُستخدم مصطلح UDF كاسم عام، بينما MVI هو تنفيذ محدد.

متى يكون UDF مفرطًا؟

UDF مفرط للشاشات التي تحتوي على حقل إدخال واحد بدون تحقق، والصفحات الثابتة، وشاشات العناصر النائبة. إذا لم يكن للشاشة منطق أعمال ولا تعتمد حالتها على إجراءات المستخدم، يضيف UDF كودًا غير ضروري بدون فائدة. لمثل هذه السيناريوهات، يكفي الربط أحادي الاتجاه أو @State بسيط في SwiftUI. يكون UDF مبررًا عندما يتجاوز عدد حالات الشاشة المحتملة 3–4 و/أو توجد آثار جانبية.

كيف تختبر UDF؟

نظرًا لأن Reducer هي دالة نقية، فإن اختبارها يتلخص في استدعائها بمجموعات مختلفة من State وIntent والتحقق من State وEffect الناتجين. في Android، استخدم Turbine لاختبار StateFlow: أرسل Intent، وتحقق من انبعاث State التالي. في TCA، يوجد TestStore مدمج يتحقق تلقائيًا من أنه بعد Action، تغيرت فقط حقول State المتوقعة وتم تنفيذ Effects المتوقعة فقط.

هل يمكن الجمع بين UDF والربط ثنائي الاتجاه؟

نعم، الجمع بينهما مقبول وغالبًا ما يكون مثاليًا. لحقول الإدخال داخل النموذج، استخدم الربط ثنائي الاتجاه المحلي (أو Binding في SwiftUI) لتجنب إنشاء Intent عند كل ضغطة مفتاح. عند إرسال النموذج، أرسل Intent واحدًا مع البيانات المجمعة، والتي تتم معالجتها بواسطة Reducer. هذا النهج الهجين — UDF عالمي مع ربط ثنائي الاتجاه محلي — يُستخدم في 70% من تطبيقات SwiftUI التجارية (بيانات Swift Community Survey 2024).

ما المشترك بين UDF وRedux وElm؟

جميع الأنماط الثلاثة تنفذ تدفق بيانات أحادي الاتجاه مع مصدر واحد للحقيقة. Elm (2012) — لغة وظيفية — قدمت لأول مرة الدورة النقية Model → View → Update. قام Redux (2015) بتكييف Elm لجافا سكريبت بمفاهيم Store وReducer وAction. UDF هو تعميم لهذه الأفكار لتطوير الأجهزة المحمولة. تضمن المناهج الثلاثة قابلية التنبؤ بالتغييرات من خلال تحديثات الحالة الذرية.

الخلاصة

  • تدفق البيانات أحادي الاتجاه (UDF) هو نمط بدورة أحادية الاتجاه State → View → Intent → Reducer، يضمن تغييرات حالة يمكن التنبؤ بها.
  • في Android، يتم تنفيذ UDF عبر ViewModel + StateFlow + sealed class Intent؛ في iOS — عبر TCA (Reducer + Store) أو Observable الأصلي.
  • توصي Google باستخدام UDF كبنية رئيسية لـ Jetpack Compose، بدءًا من 2023.
  • Reducer هي دالة نقية بدون آثار جانبية؛ يتم نقل جميع طلبات الشبكة وعمليات قاعدة البيانات إلى طبقة Effect.
  • يقضي UDF على مشكلة الحلقات اللانهائية للربط ثنائي الاتجاه من خلال معالجة Intent الصريحة ومصدر واحد للحقيقة.
  • المخاطر الرئيسية — الآثار الجانبية في Reducer، Intents مفصلة بشكل مفرط، والكوروتينات غير المكتملة.
  • النهج الهجين (الربط ثنائي الاتجاه المحلي في النماذج + UDF العالمي) مثالي لمعظم التطبيقات التجارية.

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

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

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

اقرأ أيضًا