الإعادة هي مثل حاويات الحالة في SwiftUI. الأساسيات

صورة

سنتحدث هذا الأسبوع عن إنشاء حاوية حالة مماثلة لتلك المستخدمة من قبل Redux . هذا هو المصدر الوحيد للقيمة للتطبيق قيد التطوير. حالة واحدة للتطبيق بأكمله يجعل التصحيح والتحقق أسهل. يحل مصدر وحيد لقيم الحقيقة الآلاف من الأخطاء التي تحدث عند إنشاء حالات متعددة في تطبيق ما.

مصدر واحد لقيم الحقيقة


الفكرة الرئيسية هي وصف حالة التطبيق بأكمله من خلال بنية واحدة أو تكوين الهياكل. لنفترض أننا نعمل على تطبيق بحث مستودع تخزين Github ، حيث الحالة عبارة عن مجموعة من مستودعات التخزين التي نختارها وفقًا لطلب معين باستخدام واجهة برمجة تطبيقات Github.

struct AppState { var searchResult: [Repo] = [] } 

الخطوة التالية هي تمرير الحالة (للقراءة فقط) إلى كل طريقة عرض داخل التطبيق. أفضل طريقة لتحقيق ذلك هي استخدام بيئة SwiftUI. يمكنك أيضًا تمرير كائن يحتوي على حالة التطبيق بالكامل إلى بيئة طريقة العرض الأساسية. ستقوم طريقة العرض الأساسية بمشاركة البيئة مع كل المشاهدات الفرعية. لمعرفة المزيد عن SwiftUI Environment ، تحقق من قوة البيئة في منشور SwiftUI .

 final class Store: ObservableObject { @Published private(set) var state: AppState } 

في المثال أعلاه ، أنشأنا كائنًا يخزن حالة التطبيق ويوفر وصولًا للقراءة فقط إليه. تستخدم خاصية State مجمِّع الخاصية Published ، والتي تُعلم SwiftUI بأي تغييرات. يتيح لك تحديث التطبيق بالكامل باستمرار ، واستخلاصه من مصدر واحد لقيم الحقيقة. لقد تحدثنا في وقت سابق عن كائنات التخزين في المقالات السابقة.لمعرفة المزيد حول هذا ، تحتاج إلى قراءة مقال " حالة تطبيق النمذجة باستخدام كائنات المتجر في SwiftUI ".

المخفض والإجراءات


حان الوقت للحديث عن تصرفات المستخدم التي تؤدي إلى تغييرات الحالة. الإجراء هو تعداد بسيط أو مجموعة من الأعداد التي تصف تغيير الحالة. على سبيل المثال ، قم بتعيين قيمة التحميل أثناء أخذ عينات البيانات ، قم بتعيين المستودعات الناتجة إلى خاصية الحالة ، إلخ. الآن خذ بعين الاعتبار نموذج التعليمات البرمجية للحصول على تعداد العمل.

 enum AppAction { case search(query: String) case setSearchResult(repos: [Repo]) } 

Reducer هي وظيفة تأخذ الحالة الحالية وتطبق إجراءً على الحالة وتولد حالة جديدة. عادة ما يكون المخفض أو تكوين المخفضات هو المكان الوحيد في أي تطبيق تتغير فيه الحالة. حقيقة أن وظيفة واحدة يمكنها تغيير الحالة الكاملة للتطبيق تجعل الكود بسيطًا جدًا وسهل الاختبار وسهل التصحيح. فيما يلي مثال على وظيفة الاختزال.

 struct Reducer<State, Action> { let reduce: (inout State, Action) -> Void } let appReducer: Reducer<AppState, AppAction> = Reducer { state, action in switch action { case let .setSearchResults(repos): state.searchResult = repos } } 

تدفق أحادي الاتجاه


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

حالة -> عرض -> إجراء -> حالة -> عرض

تعمل هذه البنية فقط حول دفق بيانات أحادي الاتجاه. هذا يعني أن جميع البيانات في التطبيق تتبع نفس النمط ، مما يجعل منطق التطبيق الذي تم إنشاؤه أكثر قابلية للتنبؤ وأسهل للفهم. دعنا نغير كائن المتجر لدعم إجراءات التقديم.

 final class Store<State, Action>: ObservableObject { @Published private(set) var state: State private let appReducer: Reducer<State, Action> init(initialState: State, appReducer: @escaping Reducer<State, Action>) { self.state = initialState self.appReducer = appReducer } func send(_ action: Action) { appReducer.reduce(&state, action) } } 

آثار جانبية


لقد طبقنا بالفعل دفقًا أحادي الاتجاه يقبل إجراءات المستخدم وحالة التغييرات ، ولكن ماذا عن إجراء غير متزامن ، والذي نسميه عادةً تأثيرات جانبية . كيفية إضافة دعم المهام غير المتزامن لنوع التخزين المستخدم؟ أعتقد أن الوقت قد حان لتقديم استخدام " إطار الجمع" ، والذي يعد مثالياً للتعامل مع المهام غير المتزامنة.

 import Foundation import Combine protocol Effect { associatedtype Action func mapToAction() -> AnyPublisher<Action, Never> } enum SideEffect: Effect { case search(query: String) func mapToAction() -> AnyPublisher<Action, Never> { switch self { case let .search(query): return dependencies.githubService .searchPublisher(matching: query) .replaceError(with: []) .map { AppAction.setSearchResults(repos: $0) } .eraseToAnyPublisher() } } } 

أضفنا دعمًا لمهام المزامنة من خلال تقديم بروتوكول التأثير. التأثير هو تسلسل إجراءات يمكن نشره باستخدام نوع الناشر من إطار دمج . يتيح لك ذلك معالجة المهام غير المتزامنة باستخدام Combine ، ثم نشر الإجراءات التي سيستخدمها المخفض لتطبيق الإجراءات على الحالة الحالية.

 final class Store<State, Action>: ObservableObject { @Published private(set) var state: State private let appReducer: Reducer<State, Action> private var cancellables: Set<AnyCancellable> = [] init(initialState: State, appReducer: Reducer<State, Action>) { self.state = initialState self.appReducer = appReducer } func send(_ action: Action) { appReducer.reduce(&state, action) } func send<E: Effect>(_ effect: E) where E.Action == Action { effect .mapToAction() .receive(on: DispatchQueue.main) .sink(receiveValue: send) .store(in: &cancellables) } } 

مثال عملي


أخيرًا ، يمكننا إكمال تطبيق بحث المستودع ، والذي يستدعي بشكل غير متزامن واجهة برمجة تطبيقات Github ويحدد المستودعات التي تطابق الاستعلام. شفرة المصدر الكاملة للتطبيق متاحة على جيثب .

 struct SearchContainerView: View { @EnvironmentObject var store: Store<AppState, AppAction> @State private var query: String = "Swift" var body: some View { SearchView( query: $query, repos: store.state.searchResult, onCommit: fetch ).onAppear(perform: fetch) } private func fetch() { store.send(SideEffect.search(query: query)) } } struct SearchView : View { @Binding var query: String let repos: [Repo] let onCommit: () -> Void var body: some View { NavigationView { List { TextField("Type something", text: $query, onCommit: onCommit) if repos.isEmpty { Text("Loading...") } else { ForEach(repos) { repo in RepoRow(repo: repo) } } }.navigationBarTitle(Text("Search")) } } } 

قسّم الشاشة إلى طريقتين: عرض الحاوية وعرض التقديم . يتحكم عرض الحاوية في الإجراءات ويحدد الأجزاء اللازمة من الحالة العامة . عرض التقديم يستقبل البيانات ويعرضها. تحدثنا بالفعل عن "المشاهدات حاوية" في المقالات السابقة ، لمعرفة المزيد ، اتبع الرابط " Introducing Container views in SwiftUI "

النتائج


لقد تعلمنا اليوم كيفية إنشاء حاوية حالة شبيهة بـ Redux مع مراعاة الآثار الجانبية . للقيام بذلك ، استخدمنا وظيفة SwiftUI Environment وإطار الجمع. آمل أن يكون هذا المقال مفيدًا.

شكرا للقراءة ، ونراكم قريبا!

Source: https://habr.com/ru/post/ar476398/


All Articles