Skip to content

Starkrimson/MovieDB

Repository files navigation

MovieDB

GitHub release (latest by date)

探索海量电影、剧集和人物!

preview

Releases 页面下载。

数据来源

TMDB themoviedb.org

TCA 中文 readme

  • State:即状态,是一个用于描述某个功能的执行逻辑,和渲染界面所需的数据的类。
struct SearchReducer: ReducerProtocol {
    struct State: Equatable {
        @BindableState var searchQuery = ""
        var list: [Find.City] = []
    }
}
  • Action:一个代表在功能中所有可能的动作的类,如用户的行为、提醒,和事件源等。
struct SearchReducer: ReducerProtocol {
    enum Action: Equatable, BindableAction {
        case binding(BindingAction<SearchState>)
        case search(query: String)
        case citiesResponse(Result<[Find.City], AppError>)
    }
}
  • Environment:一个包含功能的依赖的类,如API客户端,分析客户端等。
// SearchReducer ...

@Dependency(\.weatherClient) var weatherClient

// ...
  • Reducer:一个用于描述触发「Action」时,如何从当前状态(state)变化到下一个状态的函数,它同时负责返回任何需要被执行的「Effect」,如API请求(通过返回一个「Effect」实例来完成)。
struct SearchReducer: ReducerProtocol {
    var body: some ReducerProtocol<State, Action> {
        BindingReducer()
        Reduce { state, action in
            switch action {
            case .binding(let action):
                if action.keyPath == \.$searchQuery, state.searchQuery.count == 0 {
                    state.status = .normal
                    state.list = []
                }
                return .none
                
            case .search(let query):
                struct SearchCityId: Hashable { }
                
                guard state.status != .loading else { return .none }
                state.status = .loading
                return .task {
                    await .citiesResponse(TaskResult<[Find.City]> {
                        try await weatherClient.searchCity(query)
                    })
                }
                
            case .citiesResponse(let result):
                switch result {
                case .success(let list):
                    state.status = list.count > 0 ? .normal : .noResult
                    state.list = list
                case .failure(let error):
                    state.status = .failed(error.localizedDescription)
                    state.list = []
                }
                return .none
            }
        }
    }
}
  • Store:用于驱动某个功能的运行时(runtime)。将所有用户行为发送到「Store」中,令它运行「Reducer」和「Effects」。同时从「Store」中观测「State」,以更新UI。
SearchView(
    store: .init(
        initialState: .init(),
        reducer: WeatherReducer()
    )
)
struct SearchView: View {
    let store: StoreOf<WeatherReducer>
    
    var body: some View {
        WithViewStore(searchStore) { viewStore in
            List(viewStore.list) { item in
                Cell(...)
            }
            .searchable(text: viewStore.binding(\.$searchQuery))
            .onSubmit(of: .search) {
                viewStore.send(.search(query: viewStore.searchQuery))
            }
        }
    }
}

网络请求

struct WeatherClient {
    var searchCity: @Sendable (String) async throws -> [Find.City]
}

extension WeatherClient {
    static let live = WeatherClient(
        searchCity: { query in
            guard let q = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
                  let url = URL(string: "https://openweathermap.org/data/2.5/find?q=\(q)&appid=\(appid)&units=metric")
                else {
                throw AppError.badURL
            }
            
            let (data, _) = try await URLSession.shared.data(from: url)
            return try JSONDecoder().decode(Find.self, from: data).list
        }
    )