VergeIcon

Verge.swift

📍适用于 iOS 的高效状态管理架构 - UIKit, SwiftUI📍
_ 一种更简单的获取单向数据流的方式 _
_ 支持并发处理 _

我的开发笔记

在 SwiftUI 中使用 StoreReader

StoreReader 是一个 SwiftUI 视图,它从 Store 读取状态并根据状态更改显示内容。

首先,使用 @Tracking 宏定义您的状态

@Tracking
struct State {
  var count: Int = 0

  @Tracking
  struct NestedState {
    var isActive: Bool = false
    var message: String = "Hello, Verge!"
  }

  var nestedState: NestedState = NestedState()
}

struct MyView: View {

  // should not create store here in production code as a view is created every time to render.
  let store = Store<_, Never>(initialState: State())
  
  var body: some View {
    StoreReader(store) { state in
      VStack {
        Text("Count: \(state.count)")
        Button("Increment") {
          store.commit {
            $0.count += 1
          }
        }
        Text("Is Active: \(state.nestedState.isActive)")
        Text("Message: \(state.nestedState.message)")
        Button("Toggle Active") {
          store.commit {
            $0.nestedState.isActive.toggle()
          }
        }
      }
    }
  }
}

支持此项目

yellow-button

Verge: 适用于 SwiftUI 和 UIKit 的高性能、可扩展状态管理库

Verge 是一个高性能、可扩展的 Swift 状态管理库,其设计考虑了实际应用场景。 它提供了一种轻量级且易于使用的方法来管理您的应用程序状态,而无需复杂的 actions 和 reducers。 本指南将引导您了解在 Swift 项目中使用 Verge 的基础知识。

关键概念和动机

Verge 的设计考虑了以下概念

入门指南

要使用 Verge,请按照以下步骤操作

  1. 使用 @Tracking 宏定义状态结构体
  2. 使用您的初始状态实例化一个 Store
  3. 使用 store 实例上的 commit 方法更新状态
  4. 使用 sinkState 方法订阅状态更新

定义您的状态

创建一个状态结构体来表示您应用程序的状态。 使用 @Tracking 宏使您的状态可被 Verge 跟踪。 这允许 Verge 检测您状态的变化并在必要时触发更新。

@Tracking
struct MyState {
  var count: Int = 0 
}

实例化 Store

使用应用程序的初始状态创建一个 Store 实例。 Store 类采用两个类型参数

let store = Store<_, Never>(initialState: MyState())

更新状态

要更新您的应用程序状态,请使用 Store 实例上的 commit 方法。 commit 方法接受一个带有单个参数的闭包,该参数是对您状态的可变引用。 在闭包内部,根据需要修改状态。

store.commit {   
  $0.count += 1 
}

订阅状态更新

要在状态更改时接收更新,请使用 Store 实例上的 sinkState 方法。 此方法接受一个闭包,该闭包接收更新后的状态作为其参数。 每当状态更改时都会调用该闭包。

store.sinkState { state in
  // Receives updates of the state
}
.storeWhileSourceActive()

末尾的 storeWhileSourceActive() 调用是 Verge 提供的方法,用于自动管理订阅的生命周期。 只要源(在本例中为 store 实例)处于活动状态,它就会保留订阅。

使用 Store 的 Activity 进行事件驱动编程

在某些情况下,事件驱动编程对于创建响应迅速且高效的应用程序至关重要。 Verge 库的 Store Activity 功能旨在满足这一需求,使开发人员能够在他们的项目中无缝处理事件。

当您的应用程序需要事件驱动编程时,Store Activity 就会发挥作用。 它使您可以独立于主 store 管理来管理事件和关联的逻辑,从而促进干净且有组织的代碼结构。 这种关注点分离简化了整个开发过程,并使其更容易长期维护和扩展您的应用程序。

通过利用 Store Activity 功能,您可以有效地处理应用程序中的事件,同时保持 store 管理的完整性。 这确保了您的应用程序保持高性能和可扩展性,使您能够使用 Verge 库构建健壮且可靠的 Swift 应用程序。

这是一个使用 Store Activity 的例子

let store: Store<MyState, MyActivity>

store.send(MyActivity.somethingHappened)
store.sinkActivity { (activity: MyActivity) in
  // handle activities.
}
.storeWhileSourceActive()

将 Verge 与 SwiftUI 结合使用

要在 SwiftUI 中使用 Verge,您可以利用 StoreReader 在 SwiftUI 视图中订阅状态更新。 这是一个例子,说明如何做到这一点

import SwiftUI
import Verge

struct ContentView: View {
  @StoreObject private var viewModel = CounterViewModel()

  var body: some View {
    VStack {
      StoreReader(viewModel) { state in
        Text("Count: \(state.count)")
          .font(.largeTitle)
      }

      Button(action: {
        viewModel.increment()
      }) {
        Text("Increment")
      }
    }
  }
}

final class CounterViewModel: StoreComponentType {
  @Tracking
  struct State {
    var count: Int = 0
  }

  let store: Store<State, Never> = .init(initialState: .init())

  func increment() {
    commit {
      $0.count += 1
    }
  }
}

在此示例中,StoreReader 用于从 MyViewModel store 读取状态。 这允许您在 SwiftUI 视图中访问和显示状态。 此外,您可以通过直接调用 store 上的方法来执行操作,如示例中的按钮所示。

这个新部分将帮助用户了解如何将 Verge 与 SwiftUI 结合使用,使他们能够在 SwiftUI 视图中有效地管理状态。 如果您有任何进一步的建议或更改,请告诉我!

StoreObject 属性包装器

SwiftUI 提供了 @StateObject 属性包装器,用于创建和管理一个符合 ObservableObject 协议的给定对象的持久实例。 但是,每当 ObservableObject 更新时,StateObject 都会导致视图刷新。

在 Verge 中,我们引入了 StoreObject 属性包装器,它在视图的生命周期内实例化一个 Store 对象,但不会在 Store 更新时导致视图刷新。

当您想要以更精细的方式管理 Store,而不会在 Store 更改时导致整个视图刷新时,这非常有用。 相反,可以通过 StoreReader 处理 Store 更新。

将 Verge 与 UIKit 结合使用

这是一个 Verge 与 UIViewController 的简单用法示例

class MyViewController: UIViewController {
  
  @Tracking
  private struct State {
    var count: Int = 0
  }
  
  private let store: Store<State, Never> = .init(initialState: .init())
  
  private let label: UILabel = .init()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setupUI()
    
    // Subscribe to the store's state updates
    store.sinkState { [weak self] state in
      guard let self = self else { return }
      
      // Check if the value has been updated using ifChanged
      state.ifChanged(\.count) { count in
        self.label.text = "Count: \(count)"
      }
    }
    .storeWhileSourceActive()
  }
  
  private func setupUI() {
    // Omitted for brevity
  }
  
  private func incrementCount() {
    store.commit {
      $0.count += 1
    }
  }
}

使用 sinkStateChanged<State>ifChanged 在 UIKit 中高效更新状态

在 UIKit 中,它是事件驱动的,因此通过仅在需要时更新组件来高效地更新组件至关重要。 Verge 库提供了一种使用 sinkState 方法、Changed<State> 类型和 ifChanged 方法来实现此目的的方法。

当您使用 sinkState 方法时,您提供的闭包会接收包装在 Changed<State> 类型中的最新状态。 此包装器还包括先前的状态,允许您使用 ifChanged 方法确定哪些属性已更新。

这是一个在 UIKit 中使用 sinkStateifChanged 来有效更新组件的例子

store.sinkState {
  $0.ifChanged(\.myProperty) { newValue in
    // Update the component only when myProperty has changed
  }
}

在此示例中,仅当 myProperty 更改时才更新组件,从而确保在基于 UIKit 的应用程序中进行高效更新。

与 UIKit 相比,SwiftUI 使用声明性视图结构,这意味着减少了检查状态更改以更新视图的需要。 但是,在使用 UIKit 时,使用 sinkStateChanged<State>ifChanged 有助于保持高性能和响应迅速的应用程序。

使用 TaskManager 进行异步操作

Verge 的 Store 包含一个 TaskManager,允许您调度和管理异步操作。 此功能简化了处理异步任务的过程,同时保持它们与您的 Store 相关联。

基本用法

要使用 TaskManager,只需在您的 Store 实例上调用 task 方法,并提供一个包含异步操作的闭包

store.task {
  await runMyOperation()
}

使用键和模式进行任务管理

TaskManager 还使您能够根据键和模式管理任务。 您可以为每个任务分配一个唯一的键,并为其执行指定一个模式。 这允许您根据任务的键控制任务的执行行为。

例如,您可以使用 .dropCurrent 模式来放弃任何当前正在运行的具有相同键的任务,并立即运行新任务

store.task(key: .init("MyOperation"), mode: .dropCurrent) {
  //
}

此功能为您提供对任务执行方式的精细控制,即使在处理多个异步操作时,也能确保您的应用程序保持响应迅速和高效。

高级用法:管理复杂应用程序的多个 Store

理论上,在单个 store 中管理您的整个应用程序状态是理想的。 但是,在大型复杂应用程序中,计算复杂度可能会变得非常高,从而导致性能问题和应用程序响应速度缓慢。 在这种情况下,建议将您的状态分为多个 store,并根据需要进行集成。

通过将您的状态分为多个 store,您可以降低与状态更新相关的复杂性和开销。 每个 store 都可以管理您应用程序状态的特定部分,确保高效快速地执行更新。 这种方法还可以促进更好的组织和代码中的关注点分离,从而更容易长期维护和扩展您的应用程序。

要使用多个 store,请为应用程序状态的不同部分创建单独的 Store 实例,然后根据需要连接它们。 这可能涉及将 store 实例传递给子组件或在兄弟组件之间共享 store。 通过以这种方式构建您的应用程序,您可以确保您的应用程序状态的每个部分都得到有效且高效的管理。

在 Store 之间复制状态

要在 store 之间复制状态,您可以使用 sinkState 方法以及 ifChanged 函数,以便仅在状态更改时才触发更新。 这是一个例子

store.sinkState {
  $0.ifChanged(\.myState) { value in
    otherStore.commit {
      $0.myState = value
    }
  }
}

在此示例中,当 storemyState 的状态更改时,新值将提交给 otherStore。 这种方法允许您有效地在多个 store 之间同步状态。

使用 Derived 实现高效的计算属性

Verge 的 Derived 功能允许您基于 store 的状态创建计算属性并有效地订阅更新。 此功能可以通过减少不必要的计算和更新来帮助您优化应用程序。 Derived 的灵感来自 reselect 库,并提供类似的功能。

创建 Derived 属性

要创建派生属性,您需要使用 store.derived 方法。此方法接受一个 Pipeline 对象,该对象描述了如何生成派生数据。

let derived: Derived<Int> = store.derived(.select(\\.count))

您可以使用 selectmap 来生成派生数据。select 用于直接从状态中获取值,而 map 可用于基于状态生成新值,类似于 map 函数。

let derived: Derived<Int> = store.derived(.map { $0.count * 2 })

Pipeline 会检查派生数据是否已从先前的值更新。如果未更改,则 Derived 不会发布任何更改。

链式派生实例

您可以从现有的派生实例创建另一个派生实例,有效地将它们链接在一起。

let anotherDerived: Derived<String> = derived.derived(.map { $0.description })

订阅派生属性更新

要订阅派生属性的更新,您可以像使用 store 一样使用 sinkState 方法。

derived.sinkState { value in 
  // Handle updates of the derived property 
} 
.storeWhileSourceActive()

通过使用 Derived 来计算属性并订阅更新,您可以确保您的应用程序保持高效和高性能,避免不必要的计算和状态更新。

Normalization

VergeGroup 和 Verge 包提供了一个规范化技术库,可以高效地处理实体。
Normalization 是一个库,它在结构体中创建表格,并以拷贝高效的表格管理值类型实体。
它可以像处理值类型一样使用。这意味着我们可以将其与 Verge 一起使用,将它们引入 store 模式。
VergeNormalizationDerived 目标提供了在使用 Verge 时订阅实体更新的功能。

安装

SwiftPM

Verge 支持 SwiftPM。

感谢

作者

🇯🇵 Muukii (Hiroshi Kimura)

许可证

Verge 在 MIT 许可证下发布。