Bow Arch

Gitter bow-arch Playground

欢迎来到 Bow Arch!

Bow Arch 是一个基于纯函数式编程架构应用的库,其核心概念是Comonadic User Interfaces(共单子用户界面)。 请参阅项目网站以获取详细文档。

 

👩‍🏫 原则

🎨 视图作为状态的函数: 使用 SwiftUI,我们可以以声明式的方式创建用户界面,它是给定状态的表示。 该库更进一步,提倡创建基于不可变状态的视图。

🚧 关注点清晰分离: 库中的核心概念是状态 (state)输入 (input)调度器 (dispatcher)视图 (view)组件 (component)。 每一个概念都处理一个特定的关注点,并让我们分离代码处理应用开发不同方面的方式。

📦 模块化: 该库提倡创建组件,这些组件可以很容易地在整个应用程序中,甚至在其他应用程序中重用。 这些组件具有高度的可组合性,并让我们能够管理大型应用程序的复杂性。

可测试性: 函数式代码本身就具有可测试性; 因此,使用 Bow Arch 创建的软件易于测试。 该库还提供了您可以利用的实用程序来编写强大而富有表现力的测试。

🧩 高度多态: 该库基于抽象的、参数化的工件。 这使得这个库不仅是一个架构应用程序的库,而且是一个通过替换每个参数来创建不同架构的库。 尽管如此,该库中提供了特定的绑定,以便用户不必处理这些细节。

🧮 数学背景: Bow Arch 基于范畴论的概念,这为我们对代码的推理带来了可靠性。 然而,该库的 API 隐藏了这些概念的复杂性,用户不需要成为该主题的专家就可以在他们的应用程序中使用该库。

 

💻 如何获取

Bow Arch 可以通过 Swift Package Manager 获取,它集成在 Xcode 中。 您只需要使用 GitHub 上的存储库 URL 以及您想要使用的版本或分支。 或者,您可以在您的 Package.swift 文件中添加以下行来描述此依赖项:

.package(url: "https://github.com/bow-swift/bow-arch.git", from: "{version}")

 

👨‍💻 用法

Bow Arch 允许您根据可在应用程序之间重用的组件来架构您的应用程序。 让我们来看看您需要创建什么来构建一个步进器组件。

📋 状态

组件应该有一个在其视图中呈现的状态。 状态通常使用不可变的数据结构(通常是一个 struct)来建模。

对于我们的步进器组件,我们可以将状态建模为:

struct StepperState {
    let count: Int
}

📲 输入

下一步是建模组件可以处理的输入。 输入通常使用 enum 的 case 来描述。

在我们的示例中,该组件可以接收两个输入,对应于点击递减或递增按钮。 这些可以建模为:

enum StepperInput {
    case tapDecrement
    case tapIncrement
}

🎨 视图

定义了状态和输入后,我们可以使用 SwiftUI 渲染视图。 SwiftUI 是一个声明式框架,用于以 Swift 描述用户界面,并为 Apple 平台中的不同操作系统提供多个绑定。

我们可以将视图描述为其状态的函数,并使用一个函数来接收输入

import SwiftUI

struct StepperView: View {
    let state: StepperState
    let handle: (StepperInput) -> Void

    var body: some View {
        HStack {
            Button("-") {
                self.handle(.tapDecrement)
            }

            Text("\(state.count)")

            Button("+") {
                self.handle(.tapIncrement)
            }
        }
    }
}

🔨 调度器

输入需要转换为修改状态的动作。 这是在 Dispatcher 中完成的。 Dispatcher 是接收输入并产生动作的纯函数

typealias StepperDispatcher = StateDispatcher<Any, StepperState, StepperInput>

let stepperDispatcher = StepperDispatcher.pure { input in
    switch input {
    case .tapDecrement:
        return .modify { state in
            StepperState(count: state.count - 1)
        }^

    case .tapIncrement:
        return .modify { state in
            StepperState(count: state.count + 1)
        }^
    }
}

🧩 组件

最后,我们可以将所有内容作为一个组件组合在一起

typealias StepperComponent = StoreComponent<Any, StepperState, StepperInput, StepperView>

let stepperComponent = StepperComponent(
    initialState: StepperState(count: 0),
    dispatcher: stepperDispatcher,
    render: StepperView.init)

组件已经符合 SwiftUI View,因此它们可以被用作其他视图的一部分,或者被分配为 UIHostingController 的根视图。

let controller = UIHostingController(rootView: stepperComponent)

 

👏 感谢

我们要感谢 Arthur XavierPhil FreemanEdward Kmett,感谢他们之前在 PureScript 和 Haskell 语言中关于 Comonads 和 Comonadic UIs 的工作。 使用 optics 来分解和组合组件的灵感来自于 Stephen Cellis 和 Brandon Williams 的 Swift Composable Architecture 中 index 和 case path 的使用。 他们的工作启发了该库的创建。

 

⚖️ 许可

Copyright (C) 2020-2021 The Bow Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   https://apache.ac.cn/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.