Android / iOS / 鸿蒙 / React / Flutter 响应式编程共同点分析
由浅入深,从基本概念到源码原理,再到实战案例,系统梳理五大主流开发框架中的响应式编程共性与差异
一、什么是响应式编程?
1.1 从命令式到声明式
无论使用哪个平台,传统命令式编程的本质都是「我告诉程序每一步该做什么」:
1 | // Android 命令式:手动监听 + 条件判断 |
1 | // iOS 命令式:Target-Action |
响应式编程则将数据流(Data Stream)视为核心,用声明式方式描述「当数据变化时该做什么」:
1 | 命令式:监听 → 判断 → 执行 |
1.2 响应式编程的三大特征
| 特征 | 说明 | 跨平台体现 |
|---|---|---|
| 数据流 | 事件、状态、异步结果统一抽象为「流」 | Observable / Signal / Stream / State |
| 声明式 | 描述「是什么」而非「怎么做」 | 链式操作符、装饰器、Hooks |
| 自动传播 | 依赖变化自动触发更新 | 订阅机制、依赖收集、重新渲染 |
1.3 五大平台的响应式方案概览
| 平台 | 主要方案 | 核心类型 | 特点 |
|---|---|---|---|
| Android | RxJava / Kotlin Flow | Observable / Flow | 操作符丰富,协程整合 |
| iOS | RAC / RxSwift | Signal / Observable | 热/冷信号、Cocoa 扩展 |
| 鸿蒙 | ArkUI | @State / @Observed | 声明式 UI,装饰器驱动 |
| React | Hooks | useState / useEffect | 函数式、虚拟 DOM 驱动 |
| Flutter | Stream / ValueNotifier | Stream / ChangeNotifier | Dart 异步流、Listenable |
二、核心概念共通点
2.1 观察者模式:统一的底层基石
所有响应式实现都以观察者模式为基础:有「被观察对象」和「观察者」,数据变化时通知观察者。
1 | ┌─────────────────┐ ┌──────────────────┐ |
| 平台 | 被观察者 | 观察者 | 订阅方式 |
|---|---|---|---|
| Android RxJava | Observable | Observer | subscribe() |
| Android Flow | Flow | Collector | collect {} |
| iOS RAC | Signal / SignalProducer | Observer | observe() / start() |
| React | useState 返回值 | 组件 | 隐式(依赖 React 调度) |
| Flutter | Stream / ValueNotifier | StreamSubscription / addListener | listen() / addListener() |
| 鸿蒙 | @State 变量 | UI 组件 | 隐式(框架自动重绘) |
2.2 可取消性(Disposable / 生命周期)
订阅通常会产生「可取消」的句柄,用于在合适时机释放资源,避免内存泄漏:
| 平台 | 取消句柄 | 典型用法 |
|---|---|---|
| RxJava | Disposable | compositeDisposable.add() |
| Kotlin Flow | Job / CoroutineScope | viewModelScope.launch {} |
| RAC | Disposable | disposable.dispose() |
| React | useEffect 清理函数 | return () => cleanup() |
| Flutter | StreamSubscription | subscription.cancel() |
| 鸿蒙 | 框架管理 | 组件销毁自动解绑 |
2.3 热流 vs 冷流(部分平台)
在 RxJava、RAC、Kotlin Flow 中,流有「热」「冷」之分:
| 类型 | 含义 | 典型场景 |
|---|---|---|
| 冷流 | 每次订阅才执行,每个订阅者独立收到完整数据 | 网络请求、文件读取 |
| 热流 | 无论是否订阅都会持续发送,多订阅共享同一流 | 按钮点击、通知、UI 事件 |
React / Flutter / 鸿蒙 的「状态」更接近热流:始终有一个当前值,变化时通知依赖方。
三、各平台实现原理浅析
3.1 Android:RxJava 与 Kotlin Flow
RxJava:基于 ReactiveX 规范,Observable 链式操作符,每次操作返回新 Observable,订阅时从上游向下游传递事件。
1 | // RxJava 链式调用 |
Kotlin Flow:冷流,基于协程,背压天然支持,与 StateFlow/SharedFlow 配合做 UI 状态和事件。
1 | // Flow 冷流 + 操作符 |
LiveData:轻量级、生命周期感知,主线程回调,适合简单 UI 状态,官方推荐新项目用 Flow 替代。
3.2 iOS:ReactiveCocoa / ReactiveSwift
Signal:热信号,由 pipe() 创建,observer 控制发送。
1 | let (signal, observer) = Signal<String, Never>.pipe() |
SignalProducer:冷信号,每次 start 执行一次,适合异步任务。
1 | let producer = SignalProducer<String, Never> { obs, _ in |
3.3 鸿蒙 ArkUI:装饰器驱动的状态
ArkUI 用装饰器声明「可观察」的状态,状态变化自动触发 UI 更新:
1 | // @State:组件内部状态 |
@ObservedV2 + @Trace(V2):支持深层属性观察,解决嵌套对象不可观察的问题。
3.4 React:状态与副作用
React 的响应式体现在「状态驱动 UI」和「副作用与依赖同步」:
1 | const [keyword, setKeyword] = useState(''); |
状态变化 → 重新渲染 → 虚拟 DOM diff → 最小化 DOM 更新。
3.5 Flutter:Stream 与 Listenable
Stream:Dart 异步流,类似冷流,支持 map、where、asyncMap 等。
1 | Stream.periodic(Duration(seconds: 1)) |
ValueNotifier / ChangeNotifier:同步、轻量,配合 ValueListenableBuilder 做局部重建:
1 | final counter = ValueNotifier<int>(0); |
四、操作符与组合逻辑的共性
4.1 转换类:map / filter
| 操作符 | 含义 | Android | iOS RAC | Flutter | React |
|---|---|---|---|---|---|
| map | 值转换 | map {} | .map {} | .map() | 派生 state |
| filter | 过滤 | filter {} | .filter {} | .where() | 条件 + early return |
4.2 组合类:combineLatest / merge
多流组合在各平台均有对应能力:
| 场景 | RxJava | RAC | Flutter | React |
|---|---|---|---|---|
| 多源最新值 | combineLatest | combineLatest | RxDart combineLatest2 | 多个 useState + useEffect |
| 多流合并 | merge | merge | StreamGroup.merge | 自定义逻辑 |
4.3 扁平化:flatMap / switchMap
将「每个值 → 新流」展开,常用于搜索联想、取消旧请求:
| 平台 | 操作符 | 行为 |
|---|---|---|
| RxJava | flatMap / switchMap | switchMap 只保留最新内层流 |
| RAC | flatMap(.latest) | 新关键词取消上次请求 |
| Flutter | asyncExpand | 类似 flatMap |
| React | useEffect + 清理 | 依赖变化时清理上次 effect |
4.4 时间控制:debounce / throttle
防抖、节流在搜索、滚动等场景通用:
| 平台 | 防抖 | 节流 |
|---|---|---|
| RxJava | debounce() | throttleFirst/throttleLatest |
| RAC | debounce() | throttle() |
| Flutter | debounce from rxdart | throttle from rxdart |
| React | 自定义 setTimeout + cleanup | useThrottle / lodash |
五、源码层面的共通原理
5.1 链式结构与包装
操作符通常不修改原流,而是返回新的「包装流」,内部订阅上游并转换后传给下游:
1 | Observable.map(f) |
RAC 的 map 也是类似结构:Signal { observer in self.observe { ... observer.send(value: transform($0)) } }。
5.2 订阅传播
订阅是「从下游往上游」建立,事件是「从上游往下游」传递:
1 | subscribe(observer) |
5.3 取消传播
Disposable / Job 形成链:取消下游会向上传递,终止整条链的执行。
六、跨平台示例:搜索防抖 + 取消旧请求
6.1 Android (RxJava)
1 | RxTextView.textChanges(searchEditText) |
6.2 iOS (RAC)
1 | searchTextField.reactive.continuousTextValues |
6.3 鸿蒙 (ArkUI)
1 | keyword: string = '' |
6.4 React
1 | const [keyword, setKeyword] = useState(''); |
6.5 Flutter
1 | final _keyword = StreamController<String>.broadcast(); |
七、实际项目应用案例
7.1 登录表单校验(多字段组合)
需求:用户名 ≥3 字符、密码 ≥6 字符时,登录按钮才可点击。
| 平台 | 实现要点 |
|---|---|
| RxJava | combineLatest(username, password).map { u, p -> u.length>=3 && p.length>=6 } |
| RAC | Signal.combineLatest(username.signal, password.signal).map { … } |
| React | useMemo 派生 canLogin = user.length>=3 && pwd.length>=6 |
| Flutter | ValueNotifier 或 Stream,combineLatest2 派生 |
| 鸿蒙 | @State user/pwd,computed 或 @Computed 派生 canLogin |
7.2 多数据源合并展示
需求:本地缓存 + 网络接口合并去重后展示。
各平台通用思路:
- 本地流 + 远程流
- combineLatest / zip / merge 合并
- map 去重、排序
- 订阅结果更新 UI
7.3 列表下拉刷新 + 分页加载
需求:下拉刷新、上拉加载更多,防重复请求。
- 用
Subject/PublishSubject表示下拉、触底事件 - debounce 防抖
- flatMap/switchMap 发起请求,合并到单一列表流
- 错误重试、loading 状态管理
7.4 电商 App 购物车总价
需求:商品数量、价格变化时,实时计算总价。
- 每个商品的数量、单价作为流/状态
- combineLatest 合并所有项
- map/reduce 计算总价
- 单一订阅更新总价 UI
八、各平台选型建议
| 场景 | Android | iOS | 鸿蒙 | React | Flutter |
|---|---|---|---|---|---|
| 新项目 | 优先 Kotlin Flow | RAC/RxSwift | ArkUI 原生 | Hooks | Stream + ValueNotifier |
| 复杂异步流 | RxJava / Flow | RAC | 自定义 + Promise | useEffect + 自定义 Hook | rxdart |
| 简单 UI 状态 | StateFlow / LiveData | @Published | @State | useState | ValueNotifier |
| 跨层级状态 | ViewModel + StateFlow | 单例 + Property | @Provide/@Consume | Context/Redux | Provider/Riverpod |
九、共同的最佳实践
- 生命周期绑定:订阅要在页面/组件销毁时取消,避免泄漏。
- 线程/调度:UI 更新必须在主线程/主 Isolate,注意
observeOn/UIScheduler。 - 防抖与节流:输入、滚动等高频事件务必做时间控制。
- 取消旧请求:搜索、联想场景用 switchMap / flatMap(.latest) 或 useEffect 清理。
- 错误处理:onError / catch / retry 统一处理,避免吞掉异常。
- 弱引用:闭包中
[weak self]/WeakReference,防止循环引用。
十、总结
| 维度 | 共性 |
|---|---|
| 本质 | 观察者模式 + 数据流抽象 |
| 思想 | 声明式、数据驱动、自动传播 |
| 操作符 | map、filter、combine、flatMap、debounce 等在各平台都有对应 |
| 取消 | Disposable / 清理函数 / 生命周期绑定 |
| 热/冷 | 部分平台区分热流(共享)与冷流(按需执行) |
| 应用 | 表单校验、搜索联想、多源合并、列表分页、实时计算 |
掌握这些共性后,从一个平台迁移到另一个平台时,可以快速找到对应概念和 API,写出一致、可维护的响应式代码。