Redux seperti wadah negara di SwiftUI. Dasar-dasarnya

gambar

Minggu ini kita akan berbicara tentang membuat wadah negara yang mirip dengan yang digunakan oleh Redux . Ini adalah satu-satunya sumber nilai untuk aplikasi yang sedang dikembangkan. Status tunggal untuk seluruh aplikasi membuat proses debug dan verifikasi lebih mudah. Satu sumber nilai kebenaran menghilangkan ribuan kesalahan yang terjadi saat membuat banyak status dalam aplikasi.

Satu sumber nilai kebenaran


Gagasan utamanya adalah untuk menggambarkan keadaan keseluruhan aplikasi melalui satu struktur atau komposisi struktur. Katakanlah kita sedang berupaya membuat aplikasi pencarian repositori Github, di mana status adalah array repositori yang kita pilih sesuai dengan permintaan spesifik menggunakan API Github.

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

Langkah selanjutnya adalah melewati status (baca-saja) ke setiap tampilan dalam aplikasi. Cara terbaik untuk mencapai ini adalah dengan menggunakan Lingkungan SwiftUI. Anda juga bisa meneruskan objek yang berisi keadaan seluruh aplikasi ke Lingkungan tampilan dasar. Tampilan dasar akan membagikan Lingkungan dengan semua tampilan anak. Untuk mempelajari lebih lanjut tentang Lingkungan SwiftUI, lihat Kekuatan Lingkungan dalam publikasi SwiftUI .

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

Dalam contoh di atas, kami membuat objek toko , yang menyimpan keadaan aplikasi dan menyediakan akses hanya-baca untuk itu. Properti State menggunakan pembungkus properti @Published , yang memberi tahu SwiftUI tentang segala perubahan. Ini memungkinkan Anda untuk terus memperbarui seluruh aplikasi, memperolehnya dari satu sumber nilai kebenaran. Sebelumnya, kita berbicara tentang objek penyimpanan di artikel sebelumnya. Untuk mempelajari lebih lanjut tentang ini, Anda perlu membaca artikel " Memodelkan Status Aplikasi Menggunakan Objek Toko di SwiftUI ".

Peredam dan Tindakan


Saatnya untuk berbicara tentang tindakan pengguna yang mengarah pada perubahan status. Suatu tindakan adalah enumerasi sederhana atau kumpulan enumerasi yang menggambarkan perubahan keadaan. Misalnya, atur nilai beban selama pengambilan sampel data, tetapkan repositori yang dihasilkan ke properti negara, dll. Sekarang pertimbangkan kode sampel untuk penghitungan Tindakan.

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

Peredam adalah fungsi yang mengambil kondisi saat ini, menerapkan tindakan ke negara, dan menghasilkan negara baru. Biasanya peredam atau komposisi reduksi adalah satu-satunya tempat dalam aplikasi di mana keadaan berubah. Fakta bahwa satu fungsi dapat mengubah seluruh keadaan aplikasi membuat kode tersebut sangat sederhana, mudah diuji, dan mudah di-debug. Berikut ini adalah contoh dari fungsi pengurangan.

 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 } } 

Aliran searah


Sekarang saatnya berbicara tentang aliran data. Setiap tampilan memiliki akses hanya baca untuk menyatakan melalui objek toko. Tampilan dapat mengirim tindakan ke objek repositori. Reducer mengubah status, dan kemudian SwiftUI memberitahukan semua tampilan perubahan status. SwiftUI memiliki algoritma perbandingan yang sangat efisien, sehingga menampilkan keadaan seluruh aplikasi dan memperbarui tampilan yang berubah sangat cepat.

State -> View -> Action -> State -> View

Arsitektur ini hanya berfungsi di sekitar aliran data searah. Ini berarti bahwa semua data dalam aplikasi mengikuti pola yang sama, yang membuat logika aplikasi yang dibuat lebih dapat diprediksi dan lebih mudah dipahami. Mari kita ubah objek toko untuk mendukung tindakan pengiriman.

 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) } } 

Efek samping


Kami telah menerapkan aliran searah yang menerima tindakan pengguna dan mengubah status, tetapi bagaimana dengan tindakan async, yang biasanya kami sebut efek samping . Bagaimana cara menambahkan dukungan tugas asinkron untuk jenis penyimpanan yang digunakan? Saya pikir inilah saatnya untuk memperkenalkan penggunaan Kerangka Kerja Combine , yang ideal untuk menangani tugas-tugas yang tidak sinkron.

 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() } } } 

Kami menambahkan dukungan untuk tugas-tugas async dengan memperkenalkan protokol Efek. Efek adalah urutan Tindakan yang dapat dipublikasikan menggunakan jenis Penerbit dari kerangka kerja Combine . Ini memungkinkan Anda memproses pekerjaan asinkron menggunakan Combine, dan kemudian menerbitkan tindakan yang akan digunakan peredam untuk menerapkan tindakan ke kondisi saat ini.

 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) } } 

Contoh praktis


Akhirnya, kita dapat menyelesaikan aplikasi pencarian repositori, yang secara tidak sinkron memanggil API Github dan memilih repositori yang cocok dengan kueri. Kode sumber lengkap aplikasi tersedia di 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")) } } } 

Membagi layar menjadi dua tampilan: Tampilan Kontainer dan Tampilan Rendering . Tampilan Wadah mengontrol tindakan dan memilih bagian yang diperlukan dari negara global . Rendering View menerima data dan menampilkannya. Kami sudah membicarakan tentang Tampilan Kontainer di artikel sebelumnya, untuk mempelajari lebih lanjut, ikuti tautan “ Memperkenalkan tampilan Kontainer di SwiftUI

Kesimpulan


Hari ini kami belajar bagaimana membuat wadah keadaan seperti Redux dengan efek samping dalam pikiran . Untuk melakukan ini, kami menggunakan fungsi Lingkungan SwiftUI dan kerangka kerja Combine. Saya harap artikel ini bermanfaat.

Terima kasih telah membaca, dan sampai jumpa lagi!

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


All Articles