原文链接:www.callstack.com/blog/introd…
我们很高兴宣布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 可按自身节奏启动,就绪后将自动获取当前状态。
架构设计简洁明了:
- 在 TypeScript 中一次性定义状态结构
- 通过 CLI 自动生成原生类型
- React Native 与 Swift 均可访问同一状态
- 双向同步机制确保始终获取最新状态
底层实现中,状态存储于 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,因此可与Stepper、Slider和TextField等内置控件无缝协作。
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项目。查阅文档可获取详细指南,内容涵盖存储定义、代码生成配置及平台特定用法。