برنامه نویسی

ظرف حالت مانند Redux در SwiftUI: اتصالات.

در ماه گذشته، قدردانی عمیقی از مزایای داشتن یک منبع حقیقت واحد و یک ظرف حالت برای مدیریت کل وضعیت برنامه در یک مکان واحد به دست آوردم. من قبلاً این استراتژی را در چند برنامه قبلی خود اجرا کرده ام و قصد دارم به استفاده از آن در تمام پروژه های آینده ادامه دهم.

import Foundation

struct AppState: Equatable {
    var showsById: [Ids: Show] = [:]
    var seasons: [Ids: Season] = [:]
    var episodes: [Ids: Episode] = [:]
    var showImages: [Ids: FanartImages] = [:]
    var watchedHistory: [Ids] = []
}

enum AppAction: Equatable {
    case markAsWatched(episode: Ids, watched: Bool)
}

import SwiftUI
import KingfisherSwiftUI

struct HistoryView: View {
    @ObservedObject var store: Store<AppState, AppAction>

    var body: some View {
        LazyVGrid(columns: [.init(), .init()]) {
            ForEach(store.state.watchedHistory, id: \.self) { id in
                posterView(for: id)
            }
        }
    }

    private func posterView(for id: Ids) -> some View {
        let image = store.state.showImages[id]?.tvPosters?.first?.url
        let episode = store.state.episodes[id]
        let title = episode?.title ?? ""
        let date = episode?.firstAired ?? Date()

        return VStack {
            image.map {
                KFImage($0)
            }

            Text(title)
            Text(verbatim: DateFormatter.shortDate.string(for: date))
                .foregroundColor(.secondary)
                .font(.subheadline)
        }
    }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مسئله اصلی در اینجا منطق قالب بندی است که در داخل نما وجود دارد. ما نمی‌توانیم آن را با استفاده از تست‌های واحد تأیید کنیم. مشکل دیگر پیش نمایش SwiftUI است. ما باید کل فروشگاه را با کل وضعیت برنامه ارائه کنیم تا یک صفحه نمایش واحد ارائه شود. و ما نمی توانیم نمای را در یک بسته سوئیفت جدا نگه داریم زیرا به کل وضعیت برنامه بستگی دارد.

ما می‌توانیم با استفاده از فروشگاه‌های مشتق شده که فقط بخش مورد نیاز حالت برنامه را ارائه می‌کنند، وضعیت را کمی بهبود ببخشیم. اما هنوز باید منطق قالب بندی را در جایی خارج از نما نگه داریم.

اجازه دهید مؤلفه دیگری را معرفی کنم که بین کل فروشگاه برنامه و نمای اختصاصی زندگی می کند. مسئولیت اصلی این مؤلفه تبدیل وضعیت برنامه به حالت مشاهده است. من آن را Connector می نامم و جزء الهام گرفته از Redux است.

protocol Connector {
    associatedtype State
    associatedtype Action
    associatedtype ViewState: Equatable
    associatedtype ViewAction: Equatable

    func connect(state: State) -> ViewState
    func connect(action: ViewAction) -> Action
}

extension Store {
    func connect<C: Connector>(
        using connector: C
    ) -> Store<C.ViewState, C.ViewAction> where C.State == State, C.Action == Action {
        derived(
            deriveState: connector.connect(state: ),
            embedAction: connector.connect(action: )
        )
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Connector یک پروتکل ساده است که دو تابع را تعریف می کند. اولی کل حالت برنامه را به حالت view تبدیل می کند و دومی اقدامات view را به اقدامات برنامه تبدیل می کند. بیایید دیدگاه خود را با معرفی عملکردهای view و view اصلاح کنیم.

extension HistoryView {
    struct State: Equatable {
        let posters: [Poster]

        struct Poster: Hashable {
            let ids: Ids
            let imageURL: URL?
            let title: String
            let subtitle: String
        }
    }

    enum Action: Equatable {
        case markAsWatched(episode: Ids)
    }

    typealias ViewModel = Store<State, Action>
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما یک مدل کاملا متفاوت برای نمای خود ایجاد می کنیم که تنها داده های مورد نیاز را در خود نگه می دارد. حالت view در اینجا یک نگاشت مستقیم از نمایش نمای و مدل آن است. view action enum تنها اقدامی است که برای این نمای خاص موجود است. شما حوادثی را که در آنها اقدامات نامرتبط می نامید، حذف می کنید. در نهایت، نمای شما کاملاً مستقل است که به شما امکان می دهد آن را در یک بسته سوئیفت جداگانه استخراج کنید.

import KingfisherSwiftUI
import SwiftUI

struct HistoryView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        LazyVGrid(columns: [.init(), .init()]) {
            ForEach(viewModel.state.posters, id: \.title) { poster in
                VStack {
                    poster.imageURL.map {
                        KFImage($0)
                    }
                    Text(poster.title)
                    Text(poster.subtitle)
                }.onTapGesture {
                    viewModel.send(.markAsWatched(episode: poster.ids))
                }
            }
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یکی دیگر از مزایای اینجا است نمای ساده. هیچ کاری نمی کند. نمایش داده های قالب بندی شده را نمایش می دهد و اقدامات را ارسال می کند. شما می توانید به سرعت به تعداد مورد نیاز پیش نمایش SwiftUI بنویسید تا همه موارد مختلف مانند بارگیری، خالی و غیره را پوشش دهید.

extension Store {
    static func stub(with state: State) -> Store {
        Store(
            initialState: state,
            reducer: .init { _, _, _ in Empty().eraseToAnyPublisher() },
            environment: ()
        )
    }
}

struct HistoryView_Previews: PreviewProvider {
    static var previews: some View {
        HistoryView(
            viewModel: .stub(
                with: .init(
                    posters: [
                        .init(
                            ids: Ids(trakt: 1),
                            imageURL: URL(
                                staticString: "https://domain.com/image.jpg"
                            ),
                            title: "Film",
                            subtitle: "Science"
                        )
                    ]
                )
            )
        )
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

زمان ایجاد نوع اتصال دهنده خاص است که از آن برای متصل کردن حالت برنامه به حالت view استفاده می کنیم.

enum Connectors {}

extension Connectors {
    struct WatchedHistoryConnector: Connector {
        func connect(state: AppState) -> HistoryView.State {
            .init(
                posters: state.watchedHistory.compactMap { ids in
                    let episode = state.episodes[ids]
                    return HistoryView.State.Poster(
                        ids: ids,
                        imageURL: state.showImages[ids]?.tvPosters?.first?.url,
                        title: episode?.title ?? "",
                        subtitle: DateFormatter.shortDate.string(for: episode?.firstAired) ?? ""
                    )
                }
            )
        }

        func connect(action: HistoryView.Action) -> AppAction {
            switch action {
            case let .markAsWatched(episode):
                return AppAction.markAsWatched(episode: episode, watched: true)
            }
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همانطور که در مثال بالا می بینید، WatchedHistoryConnector یک نوع مقدار ساده است که می توانیم به سرعت با استفاده از تست واحد آن را آزمایش کنیم. اکنون، باید نگاهی به نحوه استفاده از انواع کانکتور خود بیندازیم. معمولاً من کانتینر یا نماهای جریانی دارم که نماها را به فروشگاه متصل می کند.

import SwiftUI

struct RootContainerView: View {
    @EnvironmentObject var store: Store<AppState, AppAction>

    var body: some View {
        HistoryView(
            viewModel: store.connect(using: Connectors.WatchedHistoryConnector())
        )
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


مخاطب
من تمرکز واضحی بر زمان عرضه به بازار دارم و بدهی فنی را در اولویت قرار نمی دهم. و من در فعالیت های پیش فروش/RFX به عنوان معمار سیستم، تلاش های ارزیابی برای موبایل (iOS-Swift، Android-Kotlin)، Frontend (React-TypeScript) و Backend (NodeJS-.NET-PHP-Kafka-SQL) شرکت کردم. -NoSQL). و همچنین کار پیش فروش را به عنوان یک مدیر ارشد فناوری از فرصت تا پیشنهاد از طریق انتقال دانش به تحویل موفق تشکیل دادم.

🛩️ #استارتاپ ها #مدیریت #cto #swift #typescript #پایگاه داده
📧 ایمیل: sergey.leschev@gmail.com
👋 لینکدین: https://www.linkedin.com/in/sergeyleschev/
👋 LeetCode: https://leetcode.com/sergeyleschev/
👋 توییتر: https://twitter.com/sergeyleschev
👋 Github: https://github.com/sergeyleschev
🌎 وب سایت: https://sergeyleschev.github.io

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا