【翻译】介绍Brownie:适用于React Native改造项目的类型安全共享状态方案

18 阅读5分钟

原文链接:www.callstack.com/blog/introd…

作者:Oskar Kwaśniewski

我们很高兴宣布Brownie(@callstack/brownie)的Alpha版发布,这是专为React Native改造项目设计的共享状态管理库。

问题所在

Brownfield现有原生应用在集成React Native后面临根本性挑战:状态共享问题

原生设置界面显示用户资料,React Native结账流程需要相同用户数据。Swift中刷新后的认证令牌需立即在React Native中生效,某处修改的主题偏好应同步至所有界面。

基于服务客户的经验,我们发现一个反复出现的模式:每个应用最终都会为数据共享构建专属的Turbo模块。各团队都在重复造轮子:编写定制原生模块、在两端维护类型定义,并随着需求变化不断更新这些模块。

其中一个典型案例是Zalando的Brownfield集成项目,详情可参阅此处

这正是当今Brownfield开发的核心痛点。这些自建解决方案存在三大缺陷:

  • 构建耗时:编写Turbo模块需要掌握各平台的原生技术。
  • 维护繁琐:每次模式变更都需同步更新TypeScript、Swift和Kotlin代码。
  • 难以保持同步:类型漂移导致运行时崩溃。
  • 重复建设:每个Brownfield团队都在从零开始解决相同问题。

我们的方法

Brownie 另辟蹊径。我们没有将 React Native 和原生代码视为两个独立的世界,而是创建了一个可同时从 TypeScript 和 Swift 访问的单一数据源。

关键的设计决策在于状态驻留在原生端。这意味着您的原生应用可以独立于 React Native 读写数据存储。React Native 可按自身节奏启动,就绪后将自动获取当前状态。

架构设计简洁明了:

  1. 在 TypeScript 中一次性定义状态结构
  2. 通过 CLI 自动生成原生类型
  3. React Native 与 Swift 均可访问同一状态
  4. 双向同步机制确保始终获取最新状态

底层实现中,状态存储于 C++ 层,通过 JSI 暴露给 JavaScript,并经由 Objective-C++ 桥接器提供给 Swift。

工作原理

1. 定义存储

创建一个包含状态结构的 *.brownie.ts 文件:

// AppStore.brownie.ts
import type { BrownieStore } from '@callstack/brownie';

interface AppStore extends BrownieStore {
  counter: number;
  user: {
    name: string;
    email: string;
  };
  settings: {
    theme: 'light' | 'dark';
    notificationsEnabled: boolean;
  };
}

declare module '@callstack/brownie' {
  interface BrownieStores {
    AppStore: AppStore;
  }
}

2. 构建 XCFrameworks

brownfield CLI 通过单一步骤处理代码生成和打包:

npx brownfield package:ios --scheme YourScheme --configuration Release

这将从您的.brownie.ts文件生成Swift类型,并将所有内容构建为XCFrameworks:

  • YourScheme.xcframework - 您的React Native模块
  • Brownie.xcframework - 共享状态库
  • ReactBrownfield.xcframework - 现有项目集成
  • hermesvm.xcframework - JavaScript引擎

将这些文件拖入您的原生Xcode项目,并设置为嵌入与签名

生成的Swift结构体与您的TypeScript模式完全匹配:

// Generated automatically
struct AppStore: Codable, Equatable {
    var counter: Double
    var user: User
    var settings: Settings
}

struct User: Codable, Equatable {
    var name: String
    var email: String
}

注:生成的代码已包含在 Brownie.xcframework

3. 在 React Native 中使用

useStore 钩子提供了熟悉的 React 模式,并支持选择器:

import { useStore } from '@callstack/brownie';

function Counter() {
  const [counter, setState] = useStore('AppStore', (s) => s.counter);

  return (
    <Button
      title={`Count: ${counter}`}
      onPress={() => setState((prev) => ({ counter: prev.counter + 1 }))}
    />
  );
}

选择器确保组件仅在其选定切片发生变化时重新渲染,这正是现代状态库应有的表现。

4. 在 SwiftUI 中使用

@UseStore 属性包装器为 SwiftUI 带来了同样的易用性:

import Brownie

struct CounterView: View {
  @UseStore(\\AppStore.counter) var counter

  var body: some View {
    VStack {
      Text("Count: \\(Int(counter))")

      Button("Increment") {
        $counter.set { $0 + 1 }
      }
    }
  }
}

KeyPath 选择器提供类型安全的访问和最佳的重新渲染效果。映射值($counter) 返回标准的 SwiftUI Binding,因此可与StepperSliderTextField等内置控件无缝协作。

5. UIKit 支持

对于 UIKit 应用,请使用基于订阅的 API:

class CounterViewController: UIViewController {
  private var store: Store<AppStore>?
  private var cancelSubscription: (() -> Void)?

  override func viewDidLoad() {
    super.viewDidLoad()
    store = StoreManager.get(key: AppStore.storeName, as: AppStore.self)

    cancelSubscription = store?.subscribe(\\.counter) { [weak self] counter in
      self?.updateLabel(counter)
    }
  }

  deinit {
    cancelSubscription?()
  }
}

我们为何打造此工具

在Callstack,我们协助众多团队将React Native集成到现有原生应用中。目睹相同模式反复出现(团队为数据共享构建定制Turbo模块,随后持续投入精力维护),我们决心彻底解决这一问题。

Brownie用标准化方案取代了这些专有数据共享模块。您无需再编写和维护自定义原生代码进行状态同步,只需用TypeScript定义数据结构,其余工作由Brownie自动处理。

我们期望实现:

  • 消除定制原生模块:无需再为数据共享编写Turbo模块
  • 原生体验无缝衔接:JS端采用React钩子,Swift端使用属性封装器
  • 端到端类型安全:TypeScript模式自动生成Swift类型
  • 自动保持同步:单一模式+自动生成的类型,无需手动更新

当前状态:Alpha版(仅限iOS)

这是早期Alpha版本。我们优先推出iOS支持以收集反馈并迭代API,随后将扩展至Android平台。

当前可用功能:

  • SwiftUI集成(通过@UseStore属性包装器)
  • UIKit集成(基于订阅的API)
  • React Native useStore钩子(支持选择器)
  • 多存储库支持
  • 嵌套对象与复杂类型处理
  • Swift类型自动代码生成
  • 通过CLI进行XCFramework打包

已知限制:

  • 仅支持iOS(Android版本即将推出)
  • 更新时执行完整状态同步(计划优化为部分同步)
  • API可能根据反馈调整

我们期待您的反馈

这是早期Alpha版本,我们正在积极收集反馈。若您尝试使用Brownie,请告知我们哪些功能正常运作、哪些存在问题,以及您的使用场景中还缺少哪些功能。

请在GitHub上创建问题提交您的想法。

Brownie隶属于React Native Brownfield项目。查阅文档可获取详细指南,内容涵盖存储定义、代码生成配置及平台特定用法。