跨平台状态管理:React / Next.js / Flutter / Swift / RxSwift / SwiftUI / ArkTS / RxJava / Agera / Jetpack Compose

由浅入深,从基本概念到源码原理,再到实战案例,系统梳理 Web、移动、原生各平台的状态管理方案


一、什么是状态管理?

1.1 状态(State)的本质

状态是应用在某一时刻的数据快照,它决定了 UI 的展示内容和行为。任何会随时间变化的数据——用户输入、网络请求结果、权限、主题——都可视为状态。

1
2
3
4
5
6
7
8
9
+------------+   +------------+   +------------+   +------------+
| User Input | | Biz Data | | UI State | | Server |
| Form/Search| | List/Detail| |Modal/Load | | Cache |
+------+-----+ +------+-----+ +------+-----+ +------+-----+
| | | |
+-----------------+--------+--------+-----------------+
|
v
UI 渲染 / 副作用执行

用户输入·业务数据·UI状态·服务端 → 汇聚为统一的 UI 渲染与副作用执行

1.2 为什么需要状态管理?

痛点 说明 状态管理的价值
散落 状态分散在各处,难以追踪 集中、可预测的数据流
同步 多处 UI 依赖同一数据,容易不一致 单一数据源(Single Source of Truth)
生命周期 状态何时创建、何时销毁、何时持久化 与组件/页面生命周期绑定
跨层级 深层级组件需要访问顶层状态 提供 Context / Provider / 依赖注入
可测试 业务逻辑与 UI 耦合,难以单测 状态逻辑可独立测试

1.3 各平台状态管理方案概览

平台/框架 主要方案 核心机制 特点
React Hooks (useState/useReducer) 虚拟 DOM diff + 依赖收集 函数式、声明式、生态丰富
Next.js Server Components + Client State 服务端/客户端分离 减少客户端 JS、流式渲染
Flutter Provider / Riverpod / Bloc InheritedWidget / Listenable Dart 异步流、可组合
Swift Combine / 手动 KVO 发布-订阅 系统级、与 SwiftUI 整合
RxSwift Observable / Subject 响应式流 操作符丰富、事件驱动
SwiftUI @State / @Binding / @Observable 声明式 + 属性包装器 数据驱动视图、自动重绘
ArkTS @State / @Prop / @Link 装饰器 + 响应式 鸿蒙声明式 UI
RxJava Observable / Subject 响应式流 异步编排、背压支持
Agera Observable / Updatable 推事件-拉数据 Google 早期方案,已归档
Jetpack Compose remember / mutableStateOf 重组(Recomposition) 声明式、与 Kotlin 协程整合

二、状态管理的核心原则与模式

2.1 单向数据流(Unidirectional Data Flow)

大多数现代框架采用「单向数据流」:状态向下流动,事件向上反馈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────┐
│ State │ ← 单一数据源
└──────┬──────┘
│ 只读传递
▼
┌─────────────┐
│ View │ → 用户操作
└──────┬──────┘
│ 事件 / Action
▼
┌─────────────┐
│ Reducer / │ → 计算新状态
│ setState │
└──────┬──────┘
│
└──────────► 更新 State → 重新渲染

React、Redux、Flutter Bloc、Jetpack Compose 的 ViewModel 均遵循此模式。

2.2 观察者模式:统一的底层基石

状态管理大多基于观察者模式:被观察对象变化时,通知所有订阅者。

平台 被观察者 观察者 通知方式
React useState 返回值 组件函数 调度重渲染
SwiftUI @State / @Published 视图 body 自动重算 body
Jetpack Compose mutableStateOf Composable 重组(Recomposition)
Flutter ChangeNotifier addListener notifyListeners
RxJava Observable Observer onNext
ArkTS @State 变量 组件 build 框架触发重绘

2.3 局部状态 vs 全局状态

类型 范围 典型方案 场景
局部状态 单组件/单页面 useState / @State / remember 输入框、折叠面板、动画
共享状态 多组件/跨页面 Context / Provider / ViewModel 主题、用户信息、购物车
服务端状态 与后端同步 React Query / SWR / 自定义 列表、详情、缓存

三、Web 前端:React 与 Next.js

3.1 React 状态管理

3.1.1 基本概念:useState

useState 是 React 最基础的状态 Hook,返回当前值和更新函数:

1
2
3
4
5
6
7
8
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}

原理简述:React 在内部维护 Fiber 树,每个组件对应一个 Fiber 节点,useState 将状态存储在 Fiber 的 memoizedState 链表中。更新时调度 setState,标记组件需要重渲染,之后执行 diff 并提交 DOM 变更。

3.1.2 派生状态与 useReducer

当状态逻辑复杂时,用 useReducer 集中处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}

3.1.3 跨组件共享:Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ThemeContext = createContext('light');

function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
</ThemeContext.Provider>
);
}

function Header() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>...</div>;
}

注意:Context 变化会导致所有消费该 Context 的组件重渲染,可配合 useMemo / 拆分 Provider 优化。

3.1.4 副作用与依赖:useEffect

1
2
3
4
5
6
7
8
9
10
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
fetchUsers(keyword).then(setUsers);
}, 300);
return () => clearTimeout(timer); // 清理函数
}, [keyword]);

3.2 Next.js 状态管理

Next.js 引入 Server Components 与 Client Components 的区分,状态管理策略随之变化。

3.2.1 服务端 vs 客户端状态

类型 组件 可用能力 典型场景
Server Component 默认 直接 fetch、访问 DB、无 hooks 静态内容、SEO、首屏数据
Client Component 'use client' useState、useEffect、事件处理 交互、表单、实时更新

3.2.2 组合模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/page.tsx - Server Component(默认)
async function Page() {
const data = await fetchFromDB(); // 服务端直接拉取
return (
<Layout>
<StaticContent data={data} />
<InteractiveCart /> {/* Client Component,内部用 useState */}
</Layout>
);
}

// components/InteractiveCart.tsx
'use client';
export function InteractiveCart() {
const [items, setItems] = useState([]);
return <CartUI items={items} onAdd={...} />;
}

3.2.3 Context 在 Next.js 中的使用

Context Provider 必须放在 Client Component 中:

1
2
3
4
5
6
7
8
9
10
// providers/ThemeProvider.tsx
'use client';
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

四、跨平台移动:Flutter

4.1 基本概念

Flutter 的 UI 是声明式的:给定状态,构建对应 Widget 树。状态变化触发 setState 或 notifyListeners,进而重建 Widget。

4.2 内置方案

4.2.1 StatefulWidget + setState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => _count++),
child: Text('$_count'),
);
}
}

4.2.2 ValueNotifier + ValueListenableBuilder(局部重建)

1
2
3
4
5
6
7
final counter = ValueNotifier<int>(0);

ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) => Text('$value'),
)
// 只有 ValueListenableBuilder 会重建,而非整棵子树

4.2.3 ChangeNotifier + Provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
void add(Item item) {
_items.add(item);
notifyListeners();
}
}

// 根节点
ChangeNotifierProvider(create: (_) => CartModel(), child: MyApp())

// 子节点消费
context.watch<CartModel>(); // 监听变化并重建
context.read<CartModel>(); // 仅读取,不监听

4.3 进阶:Riverpod / Bloc

  • Riverpod:编译期安全、可测试、不依赖 BuildContext
  • Bloc:事件 → 状态 的显式映射,适合复杂业务流

五、iOS 原生:Swift、RxSwift、SwiftUI

5.1 Swift 传统方式

  • 属性观察器:willSet / didSet
  • KVO:observe(_:options:changeHandler:)
  • 通知:NotificationCenter
  • Delegate / 闭包回调

5.2 RxSwift 响应式状态

RxSwift 将状态抽象为流,通过 Observable / Subject 管理:

1
2
3
4
5
6
7
8
9
10
11
// 文本框输入 → 搜索
let searchResults = searchTextField.rx.text.orEmpty
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { api.search($0) }
.observe(on: MainScheduler.instance)

searchResults
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { _, model, cell in
cell.textLabel?.text = model.name
}
.disposed(by: disposeBag)

Subject 可同时作为观察者和被观察者,常用于「桥接」命令式代码与响应式流:

1
2
3
4
5
6
7
8
let buttonTaps = PublishSubject<Void>()
buttonTaps
.flatMapLatest { api.fetchData() }
.subscribe(onNext: { updateUI($0) })
.disposed(by: disposeBag)

// 命令式触发
button.rx.tap.bind(to: buttonTaps).disposed(by: disposeBag)

5.3 SwiftUI 声明式状态

5.3.1 @State:组件内部值类型状态

1
2
3
4
5
6
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") { count += 1 }
}
}

5.3.2 @Binding:父子双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ParentView: View {
@State private var isOn = false
var body: some View {
ToggleView(isOn: $isOn) // 传递 Binding
}
}

struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("", isOn: $isOn)
}
}

5.3.3 @ObservedObject / @StateObject:引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var isLoading = false
}

struct ProfileView: View {
@StateObject private var viewModel = UserViewModel() // 创建并持有
var body: some View {
Text(viewModel.name)
}
}

struct ChildView: View {
@ObservedObject var viewModel: UserViewModel // 从父组件传入
var body: some View { ... }
}

5.3.4 @Observable(iOS 17+)

新宏 @Observable 可替代 ObservableObject,更简洁:

1
2
3
4
5
6
7
8
9
10
11
12
@Observable
class User {
var name: String = ""
var age: Int = 0
}

struct UserView: View {
@State private var user = User()
var body: some View {
Text(user.name) // 自动追踪依赖
}
}

六、Android 原生:RxJava、Agera、Jetpack Compose

6.1 RxJava 响应式状态

1
2
3
4
5
6
7
8
9
val querySubject = PublishSubject.create<String>()
val results = querySubject
.debounce(300, TimeUnit.MILLISECONDS)
.filter { it.length >= 2 }
.switchMap { api.search(it).toObservable() }
.observeOn(AndroidSchedulers.mainThread())

results.subscribe { updateUI(it) }.addTo(compositeDisposable)
querySubject.onNext(editText.text.toString())

StateFlow / SharedFlow(Kotlin Flow)是官方推荐替代 LiveData 的方案:

1
2
3
4
5
6
7
8
9
10
class ViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

fun loadData() {
viewModelScope.launch {
_uiState.value = UiState.Success(repository.fetch())
}
}
}

6.2 Agera 简介(已归档)

Agera 是 Google 早期的轻量级响应式库,采用推事件、拉数据模型:

  • Observable:广播事件
  • Updatable:监听事件,从 Repository 拉取数据
1
2
3
4
5
6
7
8
9
// 概念示例(Agera 已归档,仅作了解)
val repository = Repositories.repositoryWithInitialValue(initialData)
.observe()
.onUpdatesPerLoop()
.getFrom { fetchFromNetwork() }
.compile()

repository.addUpdatable(updatable)
// 事件触发时,Updatable 从 Repository 拉取最新数据

注意:Agera 已于 2023 年 3 月归档,新项目建议使用 Kotlin Flow / StateFlow。

6.3 Jetpack Compose 状态管理

6.3.1 remember + mutableStateOf

1
2
3
4
5
6
7
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
  • remember:在重组间保持值,避免每次重组重新创建
  • mutableStateOf:创建可观察状态,读该状态的 Composable 会在值变化时重组

6.3.2 状态提升(State Hoisting)

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
Child(count = count, onCountChange = { count = it })
}

@Composable
fun Child(count: Int, onCountChange: (Int) -> Unit) {
Button(onClick = { onCountChange(count + 1) }) {
Text("$count")
}
}

6.3.3 ViewModel + StateFlow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(MyState())
val state: StateFlow<MyState> = _state.asStateFlow()

fun update() {
_state.update { it.copy(...) }
}
}

@Composable
fun Screen(viewModel: MyViewModel = viewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
// 使用 state
}

6.3.4 配置变更保留:rememberSaveable

1
2
var count by rememberSaveable { mutableStateOf(0) }
// 屏幕旋转等配置变更后,count 会保留

七、鸿蒙:ArkTS

7.1 装饰器驱动的状态

ArkTS 通过装饰器声明「可观察」状态,状态变化自动触发 UI 更新。

7.1.1 @State:组件内部状态

1
2
3
4
5
6
7
8
9
10
11
12
@Entry
@Component
struct Counter {
@State count: number = 0
build() {
Column() {
Text(`Count: ${this.count}`)
Button('+1')
.onClick(() => { this.count++ })
}
}
}

7.1.2 @Prop:父 → 子单向

1
2
3
4
5
6
7
8
9
10
@Component
struct Child {
@Prop value: number // 父组件传入,子组件只读
build() {
Text(`${this.value}`)
}
}

// 父组件
Child({ value: this.count })

7.1.3 @Link:父子双向

1
2
3
4
5
6
7
8
9
10
@Component
struct Child {
@Link value: number // 子组件修改会同步回父组件
build() {
Button('+1').onClick(() => { this.value++ })
}
}

// 父组件
Child({ value: $count }) // $ 语法传递引用

7.1.4 @Provide / @Consume:跨层级

1
2
3
4
5
// 祖先
@Provide('theme') theme: string = 'light'

// 任意后代
@Consume('theme') theme: string

7.1.5 @Observed + @ObjectLink:嵌套对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Observed
class User {
name: string
constructor(name: string) { this.name = name }
}

@Component
struct UserView {
@ObjectLink user: User
build() {
Text(this.user.name)
.onClick(() => { this.user.name = 'New' }) // 触发更新
}
}

7.2 状态管理 V2(@ObservedV2 + @Trace)

V2 支持深层属性观察,解决嵌套对象内部属性不可观察的问题。


八、跨平台对比与选型

8.1 概念映射表

概念 React Flutter SwiftUI Jetpack Compose ArkTS
组件内状态 useState State @State remember + mutableStateOf @State
父子单向 props 构造函数参数 普通参数 参数 @Prop
父子双向 回调 + props 回调 @Binding 回调 + 参数 @Link
跨层级 Context Provider/InheritedWidget EnvironmentObject CompositionLocal @Provide/@Consume
引用类型 useRef/Context ChangeNotifier @ObservableObject ViewModel @Observed+@ObjectLink

8.2 选型建议

场景 推荐方案
简单 UI 状态 各平台内置(useState / @State / remember)
跨组件共享 Context / Provider / ViewModel / @Provide
复杂异步流 RxSwift / RxJava / Kotlin Flow
服务端数据 React Query / SWR / ViewModel + Repository
Next.js 全栈 Server Components 拉数据 + Client 管理交互态
新 Android 项目 Jetpack Compose + ViewModel + StateFlow
新 iOS 项目 SwiftUI + @Observable
鸿蒙应用 ArkUI 装饰器体系

九、源码原理浅析

9.1 React:Fiber 与 Hooks 链表

React 在 Fiber 节点上维护 memoizedState 链表,每个 Hook 对应链表中的一个节点:

1
2
3
4
5
Fiber.memoizedState → useState → useEffect → useContext → ...
│
├─ baseState
├─ baseQueue
└─ next (下一个 Hook)

setState 会将更新放入队列,调度器在合适时机执行重渲染,按顺序应用更新,保证 Hooks 调用顺序稳定。

9.2 Jetpack Compose:快照与重组

Compose 使用 Snapshot 系统追踪状态读取:

  1. mutableStateOf 创建 SnapshotMutableState
  2. 读取 state.value 时,当前 Composable 的「重组作用域」会记录对该 state 的依赖
  3. 写入 state.value = x 时,Compose 标记依赖该 state 的作用域需要重组
  4. 重组时重新执行对应 Composable,得到新 UI
1
读取 state → 记录依赖 → 写入 state → 标记无效 → 调度重组 → 重新执行 Composable

9.3 SwiftUI:@State 与依赖追踪

SwiftUI 在编译期和运行期结合,追踪 body 中对 @State 等属性的访问。当 @State 变化时,视图的 body 会重新求值,生成新的 View 描述,再与旧描述 diff 后更新真实视图。

9.4 ArkTS:装饰器与更新调度

@State 等装饰器在编译期生成观察逻辑,运行时状态变化会标记组件为脏,下一帧统一执行 build 更新 UI,类似 Flutter 的 setState + 帧调度。


十、实战案例

10.1 登录表单校验(多字段组合)

需求:用户名 ≥3 字符、密码 ≥6 字符时,登录按钮才可点击。

React:

1
2
3
4
const [user, setUser] = useState('');
const [pwd, setPwd] = useState('');
const canLogin = user.length >= 3 && pwd.length >= 6;
return <Button disabled={!canLogin}>登录</Button>;

SwiftUI:

1
2
3
4
@State private var user = ""
@State private var pwd = ""
var canLogin: Bool { user.count >= 3 && pwd.count >= 6 }
Button("登录") { }.disabled(!canLogin)

Jetpack Compose:

1
2
3
4
var user by remember { mutableStateOf("") }
var pwd by remember { mutableStateOf("") }
val canLogin = user.length >= 3 && pwd.length >= 6
Button(onClick = {}, enabled = canLogin) { Text("登录") }

ArkTS:

1
2
3
4
5
6
@State user: string = ''
@State pwd: string = ''
private get canLogin(): boolean {
return this.user.length >= 3 && this.pwd.length >= 6
}
Button('登录').enabled(this.canLogin)

10.2 搜索防抖 + 取消旧请求

React:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [keyword, setKeyword] = useState('');
const [results, setResults] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
let cancelled = false;
search(keyword).then(data => {
if (!cancelled) setResults(data);
});
return () => { cancelled = true; };
}, 300);
return () => clearTimeout(timer);
}, [keyword]);

RxSwift:

1
2
3
4
5
6
7
searchTextField.rx.text.orEmpty
.filter { $0.count >= 2 }
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { api.search($0) }
.observe(on: MainScheduler.instance)
.bind(to: resultsRelay)
.disposed(by: disposeBag)

Jetpack Compose + ViewModel:

1
2
3
4
5
6
val query = MutableStateFlow("")
val results = query
.debounce(300)
.filter { it.length >= 2 }
.flatMapLatest { repository.search(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

10.3 购物车总价实时计算

Flutter (Provider):

1
2
3
4
5
class CartModel extends ChangeNotifier {
final List<CartItem> _items = [];
double get total => _items.fold(0, (sum, item) => sum + item.price * item.qty);
void add(CartItem item) { _items.add(item); notifyListeners(); }
}

React:

1
2
3
4
5
const [items, setItems] = useState([]);
const total = useMemo(
() => items.reduce((s, i) => s + i.price * i.qty, 0),
[items]
);

10.4 Next.js 服务端数据 + 客户端状态

1
2
3
4
5
6
7
8
9
10
// app/product/[id]/page.tsx
export default async function Page({ params }: { params: { id: string } }) {
const product = await fetchProduct(params.id); // 服务端拉取
return (
<div>
<ProductInfo product={product} />
<AddToCartButton productId={product.id} /> {/* 内部用 useState 管理数量 */}
</div>
);
}

十一、总结与最佳实践

11.1 核心要点

维度 共性
本质 状态驱动 UI,变化触发更新
模式 单向数据流、观察者、单一数据源
局部 vs 全局 按范围选择合适的共享机制
生命周期 状态与组件/页面生命周期绑定,避免泄漏

11.2 最佳实践

  1. 最小化状态:能推导的不要存储,用 useMemo / computed / getter
  2. 状态提升:当多组件需要共享时,提升到共同祖先
  3. 不可变更新:避免直接修改,使用 setState/copy/扩展运算符
  4. 副作用清理:useEffect 清理、Disposable.dispose、viewModelScope
  5. 服务端状态分离:与 UI 状态区分,用专门库(React Query 等)管理
  6. 类型安全:TypeScript、Swift、Kotlin 充分利用类型约束状态结构

11.3 各平台快速对照

平台 局部状态 共享状态 异步/流式
React useState Context / Redux useEffect / React Query
Next.js useState (Client) Context (Client) Server Components + fetch
Flutter State / ValueNotifier Provider / Riverpod Stream / Future
SwiftUI @State @ObservableObject / Environment async/await
RxSwift Observable / Subject 同上 + Subject Observable 链
Compose remember + mutableStateOf ViewModel + StateFlow Flow / LaunchedEffect
ArkTS @State @Provide / @Consume Promise / async

参考资源

跨平台响应式状态管理实现原理深度分析

目录


一、响应式编程基础

1.1 核心概念

响应式编程是一种编程范式,其核心思想是:

  • 数据驱动UI:UI是数据的函数 UI = f(state)
  • 自动更新:当数据变化时,UI自动更新
  • 声明式:只关注”是什么”,不关注”怎么做”

状态管理是响应式编程的核心,解决的问题包括:

  • 状态的存储和访问
  • 状态变化的检测和通知
  • 副作用的处理
  • 状态的持久化

1.2 响应式系统的基本构成

一个完整的响应式系统通常包含以下组件:

组件 作用 实现方式
状态容器 存储应用状态 变量、对象、状态树
订阅机制 监听状态变化 观察者模式、发布-订阅模式
变更检测 检测状态变化 脏检查、依赖追踪、响应式代理
渲染引擎 更新UI 虚拟DOM、真实DOM操作、Widget重建
调度器 优化更新时机 批处理、微任务队列、动画帧

二、iOS响应式状态管理

2.1 传统KVO机制

核心原理:

  • 键值观察(Key-Value Observing)是iOS的原生响应式机制
  • 基于运行时的动态方法替换
  • 使用isa-swizzling技术实现属性观察

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 注册观察者
[object addObserver:self
forKeyPath:@"propertyName"
options:NSKeyValueObservingOptionNew
context:NULL];

// 2. 实现观察回调
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"propertyName"]) {
id newValue = change[NSKeyValueChangeNewKey];
// 更新UI
}
}

// 3. 移除观察者
- (void)dealloc {
[object removeObserver:self forKeyPath:@"propertyName"];
}

技术深度:

  • KVO通过动态创建子类实现,当对象被观察时,系统会:
    1. 创建一个继承自原类的子类
    2. 重写被观察属性的setter方法
    3. 替换对象的isa指针指向新子类
    4. 在setter中通知观察者

2.2 Combine框架

核心原理:

  • Apple在iOS 13+推出的响应式编程框架
  • 基于Publisher-Subscriber模式
  • 支持函数式的响应式操作

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 创建Publisher
let subject = PassthroughSubject<String, Never>()

// 2. 订阅
let cancellable = subject
.map { $0.uppercased() }
.filter { $0.count > 3 }
.sink {
print("Received: \($0)")
}

// 3. 发送值
subject.send("Hello")
subject.send("World")

// 4. 取消订阅
cancellable.cancel()

技术深度:

  • Combine使用协议和泛型实现类型安全
  • Publisher:数据源,负责发送值
  • Subscriber:订阅者,接收并处理值
  • Operator:操作符,对数据流进行转换
  • Cancellable:管理订阅生命周期

2.3 SwiftUI状态管理

核心原理:

  • 声明式UI框架,与响应式状态管理深度集成
  • 使用属性包装器简化状态管理
  • 基于依赖追踪的更新机制

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. @State - 本地状态
struct ContentView: View {
@State private var count = 0

var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}

// 2. @ObservableObject - 可观察对象
class UserViewModel: ObservableObject {
@Published var name: String

init(name: String) {
self.name = name
}
}

struct UserView: View {
@ObservedObject var viewModel: UserViewModel

var body: some View {
Text("Name: \(viewModel.name)")
}
}

技术深度:

  • @State:使用值类型的引用包装,支持本地状态
  • @Published:使用属性包装器实现自动通知
  • @ObservedObject:使用弱引用避免循环引用
  • @EnvironmentObject:通过环境传递共享状态

三、Flutter响应式状态管理

3.1 基础状态管理(setState)

核心原理:

  • 基于StatefulWidget和setState的基础状态管理
  • 脏检查机制触发重建
  • Element树的差异化更新

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;

void _increment() {
setState(() {
_count++;
});
}

@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}

技术深度:

  • setState:标记Element为dirty,触发下一帧重建
  • Widget树:不可变的配置树
  • Element树:管理生命周期和状态
  • RenderObject树:负责布局和渲染

3.2 InheritedWidget机制

核心原理:

  • 基于继承的状态传递机制
  • 利用Element树的层级关系
  • 实现跨组件的状态共享

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class AppState extends InheritedWidget {
final int count;
final VoidCallback increment;

const AppState({
Key? key,
required this.count,
required this.increment,
required Widget child,
}) : super(key: key, child: child);

static AppState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppState>()!;
}

@override
bool updateShouldNotify(AppState oldWidget) {
return oldWidget.count != count;
}
}

// 使用
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
int _count = 0;

void _increment() {
setState(() => _count++);
}

@override
Widget build(BuildContext context) {
return AppState(
count: _count,
increment: _increment,
child: MaterialApp(
home: HomePage(),
),
);
}
}

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appState = AppState.of(context);
return Column(
children: [
Text('Count: ${appState.count}'),
ElevatedButton(
onPressed: appState.increment,
child: Text('Increment'),
),
],
);
}
}

技术深度:

  • dependOnInheritedWidgetOfExactType:建立依赖关系
  • updateShouldNotify:决定是否通知子组件
  • Element依赖追踪:当InheritedWidget变化时,自动重建依赖的子组件

3.3 第三方状态管理库

Provider

核心原理:

  • 基于InheritedWidget的轻量级状态管理
  • 使用ChangeNotifier实现状态通知
  • 支持依赖注入和状态监听

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 1. 定义模型
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;

void increment() {
_count++;
notifyListeners();
}
}

// 2. 提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}

// 3. 消费状态
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counter, child) {
return Column(
children: [
Text('Count: ${counter.count}'),
ElevatedButton(
onPressed: counter.increment,
child: Text('Increment'),
),
],
);
},
);
}
}

技术深度:

  • ChangeNotifier:实现观察者模式
  • Consumer:订阅状态变化,只重建必要的Widget
  • Selector:更精确的依赖选择,避免不必要的重建

Riverpod

核心原理:

  • 解决Provider的依赖注入和测试问题
  • 基于ProviderContainer的状态管理
  • 支持编译时安全和懒加载

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 定义Provider
final counterProvider = StateProvider<int>((ref) => 0);

// 2. 使用Provider
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);

return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('Increment'),
),
],
);
}
}

// 3. 组合Provider
final userProvider = Provider<User>((ref) {
final userId = ref.watch(userIdProvider);
return User(id: userId);
});

技术深度:

  • ProviderScope:状态容器,支持测试和隔离
  • AutoDispose:自动清理不再使用的状态
  • Family:参数化Provider,支持动态创建
  • FutureProvider:处理异步状态
  • StreamProvider:处理流式状态

四、React响应式状态管理

4.1 基础状态管理(setState)

核心原理:

  • 基于Component和setState的状态管理
  • 虚拟DOM的差异化更新
  • 批处理优化性能

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

increment() {
this.setState(prevState => ({
count: prevState.count + 1
}));
}

render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.increment()}>Increment</button>
</div>
);
}
}

技术深度:

  • setState:异步更新,支持函数式更新
  • shouldComponentUpdate:手动优化渲染
  • PureComponent:浅比较优化
  • forceUpdate:强制更新

4.2 Hooks状态管理

核心原理:

  • React 16.8+引入的函数组件状态管理
  • 基于闭包和链表的Hook实现
  • 支持自定义Hook复用逻辑

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useState, useEffect } from 'react';

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

技术深度:

  • useState:基于Dispatcher的状态管理
  • useEffect:处理副作用,依赖数组控制执行时机
  • useContext:跨组件状态共享
  • useReducer:复杂状态逻辑管理
  • useMemo/useCallback:性能优化

4.3 Redux状态管理

核心原理:

  • 基于Flux架构的状态管理
  • 单一数据源和不可变状态
  • Reducer纯函数处理状态更新

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1. 定义Action
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 2. 定义Reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
}

// 3. 创建Store
const store = createStore(counterReducer);

// 4. 订阅状态
store.subscribe(() => {
console.log('Current state:', store.getState());
});

// 5. 分发Action
store.dispatch({ type: INCREMENT });

// 6. 在React中使用
import { connect } from 'react-redux';

function Counter({ count, increment, decrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}

const mapStateToProps = state => ({
count: state.count
});

const mapDispatchToProps = dispatch => ({
increment: () => dispatch({ type: INCREMENT }),
decrement: () => dispatch({ type: DECREMENT })
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

技术深度:

  • Store:单一数据源,持有应用状态
  • Action:描述状态变化的对象
  • Reducer:纯函数,根据Action计算新状态
  • Middleware:处理异步Action和副作用
  • CombineReducers:拆分状态逻辑

五、React Native响应式状态管理

5.1 原生状态管理

核心原理:

  • 与React相同的状态管理机制
  • 针对移动平台的优化
  • 支持原生模块的状态同步

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

function Counter() {
const [count, setCount] = useState(0);

return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 24 }}>Count: {count}</Text>
<TouchableOpacity
style={{ marginTop: 20, padding: 10, backgroundColor: '#007AFF' }}
onPress={() => setCount(count + 1)}
>
<Text style={{ color: 'white' }}>Increment</Text>
</TouchableOpacity>
</View>
);
}

技术深度:

  • Bridge:JavaScript与原生通信
  • Shadow Tree:虚拟DOM在RN中的实现
  • Batched Updates:批量更新优化
  • Native Modules:原生功能集成

5.2 第三方状态管理库

Redux

核心原理:

  • 与React Redux相同的架构
  • 针对移动场景的优化
  • 支持持久化和中间件

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 与Web React Redux相同
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(
rootReducer,
applyMiddleware(thunk)
);

// 在App中使用
import { Provider } from 'react-redux';

export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}

MobX

核心原理:

  • 基于观察者模式的状态管理
  • 响应式代理自动追踪依赖
  • 装饰器简化状态定义

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';

// 定义Store
class CounterStore {
@observable count = 0;

@action increment() {
this.count++;
}

@action decrement() {
this.count--;
}
}

const counterStore = new CounterStore();

// 使用Store
@observer
class Counter extends React.Component {
render() {
return (
<View>
<Text>Count: {counterStore.count}</Text>
<TouchableOpacity onPress={() => counterStore.increment()}>
<Text>Increment</Text>
</TouchableOpacity>
</View>
);
}
}

技术深度:

  • observable:创建响应式状态
  • action:修改状态的方法
  • computed:派生状态
  • reaction:副作用处理

Context API

核心原理:

  • React 16.3+的Context API
  • 简化跨组件状态共享
  • 替代Redux的轻量级方案

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { createContext, useContext, useState } from 'react';

// 创建Context
const CounterContext = createContext();

// 提供Context
function CounterProvider({ children }) {
const [count, setCount] = useState(0);

const value = {
count,
increment: () => setCount(count + 1),
decrement: () => setCount(count - 1)
};

return (
<CounterContext.Provider value={value}>
{children}
</CounterContext.Provider>
);
}

// 使用Context
function Counter() {
const { count, increment, decrement } = useContext(CounterContext);

return (
<View>
<Text>Count: {count}</Text>
<TouchableOpacity onPress={increment}>
<Text>Increment</Text>
</TouchableOpacity>
<TouchableOpacity onPress={decrement}>
<Text>Decrement</Text>
</TouchableOpacity>
</View>
);
}

// 在App中使用
function App() {
return (
<CounterProvider>
<Counter />
</CounterProvider>
);
}

六、鸿蒙响应式状态管理

6.1 ArkUI状态管理

核心原理:

  • 鸿蒙ArkUI框架的响应式状态管理
  • 基于数据驱动的UI更新
  • 支持声明式和命令式编程

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 1. 基本状态管理
@Entry
@Component
struct Counter {
@State count: number = 0;

build() {
Column() {
Text(`Count: ${this.count}`)
.fontSize(24)
Button('Increment')
.onClick(() => {
this.count++;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}

// 2. 全局状态管理
@StorageLink('count')
let globalCount: number = 0;

@Entry
@Component
struct GlobalCounter {
build() {
Column() {
Text(`Global Count: ${globalCount}`)
.fontSize(24)
Button('Increment')
.onClick(() => {
globalCount++;
})
}
}
}

技术深度:

  • @State:组件级状态,局部更新
  • @Prop:父组件传递的状态,单向数据流
  • @Link:双向绑定状态
  • @StorageLink:全局存储状态
  • @Provide/@Consume:跨组件状态共享

6.2 状态管理最佳实践

核心原理:

  • 结合MVVM架构
  • 使用状态管理器统一管理状态
  • 支持异步操作和副作用处理

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 定义状态管理器
class CounterStore {
private _count: number = 0;
private _observers: Set<() => void> = new Set();

get count(): number {
return this._count;
}

increment(): void {
this._count++;
this.notifyObservers();
}

decrement(): void {
this._count--;
this.notifyObservers();
}

subscribe(observer: () => void): void {
this._observers.add(observer);
}

unsubscribe(observer: () => void): void {
this._observers.delete(observer);
}

private notifyObservers(): void {
this._observers.forEach(observer => observer());
}
}

// 使用状态管理器
const counterStore = new CounterStore();

@Entry
@Component
struct Counter {
@State count: number = counterStore.count;

aboutToAppear(): void {
counterStore.subscribe(() => {
this.count = counterStore.count;
});
}

build() {
Column() {
Text(`Count: ${this.count}`)
.fontSize(24)
Button('Increment')
.onClick(() => {
counterStore.increment();
})
}
}
}

技术深度:

  • 单向数据流:状态变化 → UI更新
  • 可观测性:状态变化自动通知
  • 模块化:状态逻辑与UI分离
  • 可测试性:纯函数状态更新

七、性能优化与对比

7.1 性能优化策略

平台 优化策略 技术实现 性能提升
iOS 批处理更新 Combine Scheduler 30-40%
避免KVO滥用 使用Combine 20-30%
缓存计算值 @Published + 缓存 15-25%
Flutter 减少重建 const Widget + Key 40-50%
状态隔离 RepaintBoundary 25-35%
懒加载 FutureBuilder + 缓存 30-40%
React 避免重渲染 shouldComponentUpdate 30-40%
记忆化 useMemo + useCallback 20-30%
批量更新 React 18 Automatic Batching 15-25%
React Native 桥接优化 Hermes引擎 40-50%
减少渲染 React.memo 25-35%
原生模块 避免JSBridge 30-40%
鸿蒙 组件复用 @Builder 30-40%
状态管理 @StorageLink 20-30%
渲染优化 声明式UI 25-35%

7.2 平台对比

特性 iOS Flutter React React Native 鸿蒙
响应式模型 Combine + SwiftUI InheritedWidget + Provider setState + Hooks setState + Hooks @State + @StorageLink
状态管理库 Combine, RxSwift Provider, Riverpod, Bloc Redux, MobX, Context API Redux, MobX, Context API 内置状态管理 + 自定义Store
性能 高 高 中 中 高
开发效率 中 高 高 中 中
学习曲线 中 中 低 低 中
跨平台 否 是 否 是 否

7.3 性能瓶颈分析

iOS:

  • KVO的运行时开销
  • Combine的内存管理
  • SwiftUI的重建机制

Flutter:

  • Widget重建开销
  • 状态管理的性能开销
  • 渲染管线的优化

React:

  • 虚拟DOM的diff开销
  • 重渲染的性能损耗
  • 状态管理的复杂性

React Native:

  • JSBridge的通信开销
  • 原生模块的调用延迟
  • 渲染性能的限制

鸿蒙:

  • 状态管理的同步问题
  • 组件生命周期的管理
  • 性能优化的复杂度

八、最佳实践与架构选型

8.1 架构选型指南

应用规模 推荐架构 适用平台 优势
小型应用 基础状态管理 所有平台 简单直接,开发效率高
中型应用 Provider/Riverpod Flutter 轻量级,易于集成
Context API + useReducer React/React Native 内置方案,无需依赖
Combine + ObservableObject iOS 原生支持,性能好
@State + @StorageLink 鸿蒙 内置方案,开发简单
大型应用 Redux + 中间件 React/React Native 可预测性强,易于调试
Bloc + Stream Flutter 清晰的状态流转
RxSwift + MVVM iOS 响应式能力强
自定义Store + 状态管理 鸿蒙 可扩展性好

8.2 最佳实践

1. 状态管理分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
应用架构
┌─────────────────────────┐
│ UI Layer │
│ (View/Widget/Component) │
├─────────────────────────┤
│ State Management Layer │
│ (Store/Provider/Context) │
├─────────────────────────┤
│ Business Logic Layer │
│ (UseCase/Service) │
├─────────────────────────┤
│ Data Layer │
│ (Repository/API) │
└─────────────────────────┘

2. 状态管理原则

  • 单一数据源:避免状态分散
  • 不可变状态:确保状态变化可预测
  • 单向数据流:状态 → UI → 事件 → 状态
  • 分离关注点:业务逻辑与UI分离
  • 可测试性:状态管理逻辑可独立测试

3. 性能优化最佳实践

  • iOS:使用Combine的调度器,避免KVO滥用
  • Flutter:使用const Widget,合理使用Key,优化重建
  • React:使用memo,合理设置依赖数组,避免不必要的渲染
  • React Native:使用Hermes引擎,优化桥接通信
  • 鸿蒙:合理使用状态注解,优化组件渲染

4. 团队协作建议

  • 统一状态管理方案:团队内使用一致的状态管理库
  • 文档化状态结构:清晰记录状态的结构和流转
  • 代码规范:建立状态管理的代码规范
  • 测试策略:为状态管理逻辑编写单元测试
  • 性能监控:建立性能监控机制

总结

响应式状态管理是现代前端和移动开发的核心技术之一,不同平台有其独特的实现原理和最佳实践:

  1. iOS:从KVO到Combine再到SwiftUI,逐步演进为更现代的响应式方案
  2. Flutter:基于Widget树和InheritedWidget,构建了独特的响应式体系
  3. React:从setState到Hooks,简化了状态管理的复杂性
  4. React Native:继承了React的状态管理机制,针对移动平台进行了优化
  5. 鸿蒙:基于ArkUI框架,提供了声明式的状态管理方案

选择合适的状态管理方案需要考虑:

  • 应用规模:小型应用使用简单方案,大型应用使用复杂方案
  • 性能要求:对性能敏感的应用需要更精细的状态管理
  • 团队经验:选择团队熟悉的技术栈
  • 维护成本:考虑长期维护的复杂性

通过深入理解各平台的响应式状态管理原理,开发者可以构建更高效、更可维护的应用,提升用户体验和开发效率。

Android / iOS / 鸿蒙 / React / Flutter 响应式编程共同点分析

由浅入深,从基本概念到源码原理,再到实战案例,系统梳理五大主流开发框架中的响应式编程共性与差异


一、什么是响应式编程?

1.1 从命令式到声明式

无论使用哪个平台,传统命令式编程的本质都是「我告诉程序每一步该做什么」:

1
2
3
4
5
6
7
8
9
// Android 命令式:手动监听 + 条件判断
editText.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
val text = text.toString()
if (text.length >= 3) {
searchUsers(text)
}
}
})
1
2
3
4
5
6
// iOS 命令式:Target-Action
textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged)
func textDidChange() {
let text = textField.text ?? ""
if text.count >= 3 { searchUsers(text) }
}

响应式编程则将数据流(Data Stream)视为核心,用声明式方式描述「当数据变化时该做什么」:

1
2
命令式:监听 → 判断 → 执行
响应式:数据流 → 转换/过滤 → 订阅并响应

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
2
3
4
5
6
7
┌─────────────────┐         ┌──────────────────┐
│ 数据源/生产者 │ ──────► │ 观察者/消费者 │
│ (Observable等) │ 订阅 │ (Observer等) │
└─────────────────┘ └──────────────────┘
│ ▲
│ onNext / send / emit │
└────────────────────────────┘
平台 被观察者 观察者 订阅方式
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
2
3
4
5
6
7
8
9
10
11
// RxJava 链式调用
Observable.create<String> { emitter ->
emitter.onNext("Hello")
emitter.onComplete()
}
.map { it.uppercase() }
.filter { it.length > 0 }
.subscribe(
{ println(it) },
{ it.printStackTrace() }
)

Kotlin Flow:冷流,基于协程,背压天然支持,与 StateFlow/SharedFlow 配合做 UI 状态和事件。

1
2
3
4
5
// Flow 冷流 + 操作符
flowOf(1, 2, 3)
.map { it * 2 }
.filter { it > 2 }
.collect { println(it) }

LiveData:轻量级、生命周期感知,主线程回调,适合简单 UI 状态,官方推荐新项目用 Flow 替代。

3.2 iOS:ReactiveCocoa / ReactiveSwift

Signal:热信号,由 pipe() 创建,observer 控制发送。

1
2
3
let (signal, observer) = Signal<String, Never>.pipe()
signal.observeValues { print($0) }
observer.send(value: "Hi")

SignalProducer:冷信号,每次 start 执行一次,适合异步任务。

1
2
3
4
5
let producer = SignalProducer<String, Never> { obs, _ in
obs.send(value: "Hello")
obs.sendCompleted()
}
producer.startWithValues { print($0) }

3.3 鸿蒙 ArkUI:装饰器驱动的状态

ArkUI 用装饰器声明「可观察」的状态,状态变化自动触发 UI 更新:

1
2
3
4
5
6
7
8
9
10
11
12
// @State:组件内部状态
@State count: number = 0

// @Prop:父→子单向
// @Link:双向
// @Provide / @Consume:跨层级

// @Observed + @ObjectLink:嵌套对象
@Observed
class User {
name: string
}

@ObservedV2 + @Trace(V2):支持深层属性观察,解决嵌套对象不可观察的问题。

3.4 React:状态与副作用

React 的响应式体现在「状态驱动 UI」和「副作用与依赖同步」:

1
2
3
4
5
6
7
8
9
10
11
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

// 依赖 keyword 变化,自动重新执行
useEffect(() => {
if (keyword.length < 3) return;
const timer = setTimeout(() => {
searchUsers(keyword).then(setUsers);
}, 300);
return () => clearTimeout(timer); // 清理
}, [keyword]);

状态变化 → 重新渲染 → 虚拟 DOM diff → 最小化 DOM 更新。

3.5 Flutter:Stream 与 Listenable

Stream:Dart 异步流,类似冷流,支持 map、where、asyncMap 等。

1
2
3
4
Stream.periodic(Duration(seconds: 1))
.map((i) => i * 2)
.take(5)
.listen((value) => print(value));

ValueNotifier / ChangeNotifier:同步、轻量,配合 ValueListenableBuilder 做局部重建:

1
2
3
4
5
final counter = ValueNotifier<int>(0);
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (_, value, __) => Text('$value'),
)

四、操作符与组合逻辑的共性

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
2
3
4
5
6
7
8
9
Observable.map(f)
│
├─ 创建 MapObservable
│ │
│ ├─ source = 上游 Observable
│ └─ mapper = 转换函数 f
│
└─ subscribe 时:上游.subscribe(下游的包装 Observer)
下游 Observer 收到值时:onNext(mapper(value))

RAC 的 map 也是类似结构:Signal { observer in self.observe { ... observer.send(value: transform($0)) } }。

5.2 订阅传播

订阅是「从下游往上游」建立,事件是「从上游往下游」传递:

1
2
3
4
5
6
subscribe(observer)
→ 下游 Observable 订阅其 source
→ 再往上游订阅
→ … 直到最顶层
→ 顶层开始 onNext/onComplete
→ 层层传递到最下游

5.3 取消传播

Disposable / Job 形成链:取消下游会向上传递,终止整条链的执行。


六、跨平台示例:搜索防抖 + 取消旧请求

6.1 Android (RxJava)

1
2
3
4
5
6
7
8
RxTextView.textChanges(searchEditText)
.map { it.toString() }
.filter { it.length >= 2 }
.debounce(300, TimeUnit.MILLISECONDS)
.switchMap { keyword -> api.searchUsers(keyword).toObservable() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ users -> updateUI(users) }, { it.printStackTrace() })
.addTo(compositeDisposable)

6.2 iOS (RAC)

1
2
3
4
5
6
7
8
9
10
searchTextField.reactive.continuousTextValues
.filter { ($0 ?? "").count >= 2 }
.debounce(0.3, on: QueueScheduler.main)
.flatMap(.latest) { keyword in
searchAPI(keyword ?? "")
}
.observe(on: UIScheduler())
.observeValues { [weak self] users in
self?.updateSearchResults(users)
}

6.3 鸿蒙 (ArkUI)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@State keyword: string = ''
@State users: User[] = []
private timer: number = -1

onInputChange(value: string) {
this.keyword = value
clearTimeout(this.timer)
if (value.length < 2) return
this.timer = setTimeout(() => {
SearchAPI.searchUsers(value).then((users: User[]) => {
this.users = users
})
}, 300)
}

6.4 React

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
let cancelled = false;
searchUsers(keyword).then(data => {
if (!cancelled) setUsers(data);
});
return () => { cancelled = true; };
}, 300);
return () => clearTimeout(timer);
}, [keyword]);

6.5 Flutter

1
2
3
4
5
6
7
8
9
10
11
12
13
final _keyword = StreamController<String>.broadcast();
final _users = ValueNotifier<List<User>>([]);

_keyword.stream
.where((s) => s.length >= 2)
.transform(StreamTransformer.fromHandlers(
handleData: (data, sink) => Timer(Duration(milliseconds: 300), () => sink.add(data)),
))
.asyncMap((k) => searchAPI(k))
.listen((users) => _users.value = users);

// 输入时
_keyword.add(controller.text);

七、实际项目应用案例

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 多数据源合并展示

需求:本地缓存 + 网络接口合并去重后展示。

各平台通用思路:

  1. 本地流 + 远程流
  2. combineLatest / zip / merge 合并
  3. map 去重、排序
  4. 订阅结果更新 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

九、共同的最佳实践

  1. 生命周期绑定:订阅要在页面/组件销毁时取消,避免泄漏。
  2. 线程/调度:UI 更新必须在主线程/主 Isolate,注意 observeOn / UIScheduler。
  3. 防抖与节流:输入、滚动等高频事件务必做时间控制。
  4. 取消旧请求:搜索、联想场景用 switchMap / flatMap(.latest) 或 useEffect 清理。
  5. 错误处理:onError / catch / retry 统一处理,避免吞掉异常。
  6. 弱引用:闭包中 [weak self] / WeakReference,防止循环引用。

十、总结

维度 共性
本质 观察者模式 + 数据流抽象
思想 声明式、数据驱动、自动传播
操作符 map、filter、combine、flatMap、debounce 等在各平台都有对应
取消 Disposable / 清理函数 / 生命周期绑定
热/冷 部分平台区分热流(共享)与冷流(按需执行)
应用 表单校验、搜索联想、多源合并、列表分页、实时计算

掌握这些共性后,从一个平台迁移到另一个平台时,可以快速找到对应概念和 API,写出一致、可维护的响应式代码。


参考资源

ethers.js vs web3.js vs viem vs wagmi

1. 核心定位与层级关系

四者分属不同层级,服务于以太坊 Web3 前端开发的不同需求:

库 定位 层级 核心价值
ethers.js 以太坊 JavaScript 库 底层 提供与以太坊交互的核心功能
web3.js 以太坊 JavaScript 库 底层 以太坊基金会官方维护的全功能库
viem 以太坊 JavaScript 库 底层 现代、轻量的以太坊交互库
wagmi React Hooks 工具库 上层 基于底层库封装的前端开发工具

2. 详细功能对比

(1)ethers.js

  • 核心功能:
    • 钱包管理(生成、导入、签名交易)
    • 智能合约交互(部署、调用、监听事件)
    • 网络连接(与以太坊节点通信)
    • 以太坊数据解析(地址、交易、区块等)
  • 特点:
    • 轻量级(体积小,按需引入)
    • API 现代(使用 Promise,支持 async/await)
    • TypeScript 友好
    • 文档清晰,社区活跃
  • 使用场景:
    • 需要直接与以太坊区块链交互的项目
    • 对包体积敏感的应用
    • 追求现代 API 设计的开发者

(2)web3.js

  • 核心功能:
    • 与 ethers.js 类似,提供全面的以太坊交互功能
    • 支持更多底层 RPC 方法
    • 内置更多工具函数
  • 特点:
    • 功能全面(官方维护,覆盖所有以太坊功能)
    • 相对较重(包体积大)
    • API 设计较传统(早期版本使用回调,新版支持 Promise)
    • 历史悠久,生态成熟
  • 使用场景:
    • 需要使用官方全功能实现的项目
    • 传统 Web3 项目迁移
    • 对官方支持有强依赖的场景

(3)viem

  • 核心功能:
    • 与 ethers.js 类似,提供以太坊交互的核心功能
    • 支持 EIP-1193 钱包标准
    • 内置链数据和地址工具
  • 特点:
    • 极致轻量(体积比 ethers.js 更小)
    • 性能优化(更快的交易签名和网络请求)
    • 现代 API 设计(链式调用,类型安全)
    • 专注于前端使用场景
  • 使用场景:
    • 对包体积和性能要求极高的项目
    • 现代前端框架(如 React 18+)
    • 追求最新技术栈的开发者

(4)wagmi

  • 核心功能:
    • 基于 React Hooks 的以太坊交互封装
    • 钱包连接与管理(支持多种钱包)
    • 智能合约调用的 Hook 化(如 useContractRead)
    • 交易发送与状态管理
    • 与 @tanstack/react-query 集成,提供数据缓存
  • 特点:
    • 前端开发友好(Hooks 化 API)
    • 支持多底层库(默认支持 viem,可选 ethers.js)
    • 与现代前端生态集成(React、TypeScript)
    • 配置灵活,扩展性强
  • 使用场景:
    • React 前端 Web3 项目
    • 需要快速实现钱包连接、合约交互的场景
    • 追求开发效率和代码简洁的项目

3. 联系与依赖关系

  • 底层库之间的关系:
    • ethers.js、web3.js、viem 均为底层以太坊交互库,功能重叠但设计理念和性能不同
    • viem 是较新的库,针对前端场景优化,体积和性能优于传统库
  • wagmi 与底层库的关系:
    • wagmi v1+ 默认使用 viem 作为底层库(替代之前的 ethers.js)
    • 仍支持通过配置使用 ethers.js(兼容性考虑)
    • wagmi 将底层库的复杂逻辑封装为简洁的 React Hooks
  • 功能互补:
    • 底层库(ethers.js/web3.js/viem)负责与区块链交互的核心逻辑
    • 上层库(wagmi)简化前端开发,提供 Hooks 化 API 和状态管理
  • 生态组合:
    • 现代 Web3 前端开发的主流组合:viem + wagmi + RainbowKit(或其他钱包连接库)
    • 传统组合:ethers.js/web3.js + 自定义钱包连接逻辑

4. 技术演进与选择建议

  • 技术趋势:
    • 从 web3.js 到 ethers.js:追求更现代、轻量的 API
    • 从 ethers.js 到 viem:进一步优化性能和体积
    • 从直接使用底层库到 wagmi:提升前端开发效率
  • 选择建议:
    • 现代前端项目:推荐使用 viem + wagmi 组合
      • 优势:极致轻量、性能优异、开发效率高
    • 需要兼容性:选择 ethers.js + wagmi
      • 优势:生态成熟、文档丰富、社区支持广
    • 传统项目:继续使用 web3.js
      • 优势:官方维护、功能全面、历史兼容性好
    • 底层定制需求:直接使用 viem 或 ethers.js
      • 优势:灵活性高,可按需实现特定功能

5. 总结

  • 底层库(ethers.js/web3.js/viem):负责与以太坊区块链交互的核心逻辑,选择依据为性能、体积和 API 偏好
  • 上层库(wagmi):简化前端开发,提供 Hooks 化 API 和状态管理,大幅提升开发效率
  • 最佳实践:现代项目优先选择 viem + wagmi 组合,兼顾性能和开发体验

这种分层设计反映了 Web3 前端开发的演进趋势:从复杂的底层操作到简洁的上层抽象,从单一功能库到系统化的生态工具链。

React / Flutter 状态管理

由浅入深,从基本概念到原理与源码,再到示例与实际项目应用案例,系统梳理两大主流框架中的状态管理方案

一、状态管理基础概念

1.1 什么是状态(State)?

状态是驱动 UI 变化的数据。当状态改变时,界面随之更新,形成「数据驱动视图」的声明式模式。

1
2
3
4
状态 ──► 视图
▲ │
│ ▼
└── 用户交互 / 网络请求 / 定时器等

1.2 状态的分类

类型 作用域 典型场景 生命周期
本地状态 单组件 输入框内容、展开/折叠、选中项 跟随组件
共享状态 多组件 用户信息、主题、购物车 需要提升或全局管理
服务端状态 与后端同步 API 数据、缓存 异步、需缓存策略

1.3 为什么需要状态管理?

随着应用复杂度上升,会出现:

  • 状态提升导致 props 层层传递(prop drilling)
  • 状态分散导致难以追踪和调试
  • 重复请求、缓存失效等数据一致性问题

状态管理方案的目标:集中、可预测、易维护。


二、React 状态管理

2.1 内置方案概览

方案 适用场景 特点
useState 本地状态 简单、轻量
useReducer 复杂本地状态 可预测、易测试
Context API 跨层级共享 官方内置、易造成不必要的重渲染

2.2 useState:最简单的本地状态

1
2
3
4
5
6
7
8
9
10
11
12
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* 函数式更新,避免闭包陷阱 */}
<button onClick={() => setCount(prev => prev + 1)}>+1 (安全)</button>
</div>
);
}

惰性初始化:初始值可以是函数,仅在首次渲染执行。

1
const [state, setState] = useState(() => expensiveComputation());

2.3 useReducer:复杂状态的 reducer 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });

return (
<div>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}

2.4 Context API:跨层级共享状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ThemeContext = createContext('light');

function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}

function Page() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>...</div>;
}

注意:Provider 的 value 变化会导致所有 useContext 的消费者重渲染,需配合 useMemo 或拆分 Context 优化。

2.5 Redux / Redux Toolkit:全局状态管理

Redux 采用单向数据流:View → Action → Reducer → Store → View。

1
2
3
4
5
┌─────────┐   dispatch    ┌─────────┐   reduce    ┌────────┐
│ View │ ───────────► │ Action │ ─────────► │ Store │
└─────────┘ └─────────┘ └────────┘
▲ │
└──────────────── subscribe ─────────────────────┘

Redux Toolkit 示例(官方推荐写法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
decrement: state => { state.value -= 1; },
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
1
2
3
4
5
6
7
8
9
10
// 组件中使用
import { useDispatch, useSelector } from 'react-redux';
import { increment } from './store/counterSlice';

function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();

return <button onClick={() => dispatch(increment())}>{count}</button>;
}

2.6 Zustand:轻量级全局状态

Zustand 基于 Hooks,API 简洁,无 Provider 包裹。

1
2
3
4
5
6
7
8
9
10
11
12
import { create } from 'zustand';

const useStore = create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
}));

function Counter() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}

选择器优化:只订阅需要的字段,避免无关更新。

1
const count = useStore(state => state.count); // 仅 count 变化时重渲染

2.7 React 状态管理原理浅析

useState 的链表结构

React 内部用链表存储 Hooks。每个 Hook 对应链表中的一个节点,通过 Fiber 的 memoizedState 串联。

1
2
3
Fiber.memoizedState → Hook1 → Hook2 → Hook3 → ...
│
[state, setState]

这就是为什么 Hooks 必须在顶层调用、不能放在条件/循环中:链表顺序必须稳定。

setState 的批处理(Batching)

React 18 默认对所有更新进行自动批处理,多次 setState 会合并为一次渲染。

1
2
3
4
5
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// 仅触发一次重渲染
}

三、Flutter 状态管理

3.1 方案概览

方案 官方/社区 适用场景 特点
setState 内置 本地状态 简单,整组件重建
InheritedWidget 内置 跨层级共享 底层基础,一般不直接使用
Provider 官方推荐 中小型应用 基于 InheritedWidget,易上手
Riverpod 社区主流 中大型应用 编译期安全、可测试、无 context
Bloc 社区 复杂业务逻辑 事件驱动、可预测、适合团队
GetX 社区 快速开发 全能型,状态+路由+依赖注入

3.2 setState:最简单的本地状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
int _count = 0;

void _increment() {
setState(() {
_count++;
});
}

@override
Widget build(BuildContext context) {
return Column(
children: [
Text('$_count'),
ElevatedButton(onPressed: _increment, child: Text('+1')),
],
);
}
}

原理:setState 会标记当前 Element 为脏,在下一帧触发 build 重建子树。

3.3 Provider:官方推荐方案

Provider 基于 InheritedWidget,通过 context.watch<T>() 监听变化并重建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 定义 Model(继承 ChangeNotifier)
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;

void increment() {
_count++;
notifyListeners(); // 通知监听者
}
}

// 2. 在根节点提供
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}

// 3. 在子组件使用
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
return Text('${counter.count}');
}
}

多种 Provider 类型:

类型 用途
Provider 不可变值
ChangeNotifierProvider 可变、需 notifyListeners
FutureProvider 异步数据
StreamProvider 流数据
MultiProvider 组合多个 Provider

3.4 Riverpod:下一代状态管理

Riverpod 无 BuildContext 依赖,支持编译期类型安全、易于测试和复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 定义 Provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
}

// 2. 在 runApp 外包一层 ProviderScope
void main() {
runApp(ProviderScope(child: MyApp()));
}

// 3. 在组件中使用(无需 context)
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: Text('$count'),
);
}
}

ref 的三大方法:

方法 作用
ref.watch() 监听变化,值变化时重建
ref.read() 一次性读取,不监听
ref.listen() 监听变化并执行副作用,不重建

3.5 Bloc:事件驱动架构

Bloc 将 UI 与业务逻辑解耦,通过 Event → Bloc → State 的流程管理状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 定义 Event 和 State
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

class CounterState {
final int count;
CounterState(this.count);
}

// 2. 实现 Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<Increment>((event, emit) => emit(CounterState(state.count + 1)));
on<Decrement>((event, emit) => emit(CounterState(state.count - 1)));
}
}

// 3. 在 UI 中使用
BlocProvider(
create: (_) => CounterBloc(),
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('${state.count}');
},
),
)

3.6 Flutter 状态管理原理浅析

setState 与 Element 树

1
2
3
4
5
6
7
8
9
10
setState() 被调用
│
▼
标记 Element 为 dirty
│
▼
下一帧 SchedulerBinding 触发 build
│
▼
Element.rebuild() → State.build()

InheritedWidget 与依赖收集

InheritedWidget 通过 context.dependOnInheritedWidgetOfExactType<T>() 建立「依赖关系」。当 InheritedWidget 更新时,依赖它的 Element 会被标记为脏并重建。

Provider 的 notifyListeners() 会触发 InheritedWidget 的更新,从而通知所有 context.watch 的消费者。


四、React vs Flutter 状态管理对比

4.1 概念映射

概念 React Flutter
本地状态 useState setState
复杂本地状态 useReducer 自建 StatefulWidget + 内部逻辑
跨层级共享 Context InheritedWidget / Provider
全局 Store Redux / Zustand Provider / Riverpod / Bloc
选择器/按需订阅 useSelector / useStore(selector) context.select / ref.watch(provider.select())

4.2 设计哲学差异

维度 React Flutter
更新粒度 组件级,虚拟 DOM diff Widget 树重建,Element 复用
数据流 单向(Redux)或自由(Zustand) 多为单向,Bloc 强调事件流
依赖注入 通过 props / Context 通过 context / ref(Riverpod)
服务端状态 React Query / SWR 等 Riverpod 的 FutureProvider、flutter_bloc 等

五、源码层面的理解

5.1 React useState 的调度

React 的 setState 会调用 dispatchSetState,将更新放入 updateQueue,由调度器(Scheduler)在合适的时机批量处理,触发 render 和 commit。

1
2
3
4
5
6
// 简化流程
setState(newState)
→ enqueueUpdate(fiber, update)
→ scheduleUpdateOnFiber(fiber)
→ performConcurrentWorkOnRoot / performSyncWorkOnRoot
→ commitRoot

5.2 Flutter ChangeNotifier 与 Listenable

ChangeNotifier 继承 Listenable,内部维护 _listeners 列表。notifyListeners() 遍历并调用所有监听者。

1
2
3
4
5
6
// 简化逻辑
void notifyListeners() {
for (final listener in _listeners) {
listener(); // 触发 Consumer 等重建
}
}

Provider 的 InheritedProvider 会 addListener 到 ChangeNotifier,当 notifyListeners 被调用时,触发自身 updateShouldNotify 并重建子树。


六、实际项目应用案例

6.1 案例一:电商购物车(React + Zustand)

需求:跨页面购物车数量、增删改、持久化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// store/cartStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useCartStore = create(
persist(
(set) => ({
items: [],
addItem: (product, qty = 1) =>
set((state) => ({
items: state.items.some((i) => i.id === product.id)
? state.items.map((i) =>
i.id === product.id ? { ...i, qty: i.qty + qty } : i
)
: [...state.items, { ...product, qty }],
})),
removeItem: (id) =>
set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
totalCount: (state) => state.items.reduce((sum, i) => sum + i.qty, 0),
}),
{ name: 'cart-storage' }
)
);

// Header 中只订阅 totalCount,避免整 store 变化导致重渲染
const totalCount = useCartStore((s) =>
s.items.reduce((sum, i) => sum + i.qty, 0)
);

6.2 案例二:用户认证流(Flutter + Riverpod)

需求:登录态、token 刷新、路由守卫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// providers/auth_provider.dart
final authStateProvider = StateNotifierProvider<AuthNotifier, AsyncValue<User?>>((ref) {
return AuthNotifier(ref);
});

class AuthNotifier extends StateNotifier<AsyncValue<User?>> {
AuthNotifier(this.ref) : super(const AsyncValue.loading()) {
_init();
}
final Ref ref;

Future<void> _init() async {
final token = await storage.getToken();
if (token == null) {
state = const AsyncValue.data(null);
return;
}
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => api.getCurrentUser());
}

Future<void> login(String email, String pwd) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => api.login(email, pwd));
}

void logout() {
storage.clearToken();
state = const AsyncValue.data(null);
}
}

// 路由守卫:根据 authState 跳转登录页或首页
ref.listen(authStateProvider, (prev, next) {
next.whenData((user) {
if (user == null) navigator.pushReplacement(LoginRoute());
});
});

6.3 案例三:列表筛选与分页(React + Redux Toolkit + RTK Query)

需求:筛选条件、分页、缓存、乐观更新。

1
2
3
4
5
6
7
8
// 使用 RTK Query 管理服务端状态
const { data, isLoading, refetch } = useGetProductsQuery({
page: currentPage,
category: selectedCategory,
});

// 本地筛选状态用 Redux 或 useState 均可
const [filters, setFilters] = useState({ category: '', sort: 'default' });

6.4 案例四:主题与多语言(Flutter + Provider)

需求:亮/暗主题、中英文切换,全局生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 MultiProvider 组合
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ThemeModel()),
ChangeNotifierProvider(create: (_) => LocaleModel()),
],
child: MyApp(),
),
);

// 任意子组件
final theme = context.watch<ThemeModel>();
final locale = context.watch<LocaleModel>();

七、选型建议

场景 React 推荐 Flutter 推荐
小项目/原型 useState + Context setState + Provider
中大型项目 Redux Toolkit / Zustand Riverpod / Bloc
强类型、可测试 Redux + TypeScript / Zustand Riverpod
复杂业务流、事件驱动 Redux / XState Bloc
服务端状态 React Query / SWR Riverpod FutureProvider / dio + 自封装

八、总结

  • React:从 useState 起步,全局状态优先考虑 Redux Toolkit 或 Zustand,服务端状态用 React Query 等。
  • Flutter:从 setState 起步,共享状态用 Provider 入门,进阶用 Riverpod 或 Bloc。
  • 选型时关注:团队熟悉度、项目规模、可测试性、与框架生态的契合度。

由浅入深掌握上述方案后,可以根据具体业务灵活组合,构建可维护、可扩展的状态管理体系。

React 18:渲染入口主链路

React 18:渲染入口主链路(简化)

用户(调用 API) ReactDOM FiberRoot / HostRoot Fiber updateQueue / lanes Scheduler 调度入口 createRoot(container) createContainer(ConcurrentRoot) createFiberRoot + initializeUpdateQueue markContainerAsRoot + listenToAllSupportedEvents 返回 root(ReactDOMRoot) root.render(element) updateContainer(element, root) requestEventTime() + requestUpdateLane() createUpdate(eventTime, lane) + update.payload={element} enqueueUpdate(HostRootFiber, update, lane) scheduleUpdateOnFiber(root, hostFiber, lane, eventTime) ensureRootIsScheduled(root, eventTime) 进入并发/同步 render(Day1 Block2)

React Hooks 与 Fiber:原理与应用

一、React Hooks 的底层实现

1.1 核心问题

函数组件每次渲染都会重新执行,本身不保留状态。Hooks 如何「记住」上一次的值?

1.2 本质:链表 + 调用顺序

React 为每个组件实例维护一个 Fiber 节点,Fiber 上有 memoizedState,是一条链表:

1
2
3
4
useState(0)   → Node1 { value: 0 }
useState('') → Node2 { value: '' }
useEffect(fn) → Node3 { effect: fn }
useMemo(calc) → Node4 { value: result }

Hooks 按调用顺序依次读取/更新链表上的节点,不做「按名字查找」。

1.2.1 示例:状态如何挂在 Fiber 链表上

组件与 Fiber 一一对应:每个组件实例有一个 Fiber 节点,Fiber 的 memoizedState 指向 Hooks 链表。

以 Counter 组件为例:

1
2
3
4
5
6
function Counter() {
const [count, setCount] = useState(0); // Hook 1
const [name, setName] = useState(''); // Hook 2
const [flag, setFlag] = useState(false); // Hook 3
return <div>{count}</div>;
}

首次渲染时,React 在 Fiber 上建立如下链表结构:

1
2
3
4
5
6
7
8
9
Fiber (Counter 组件)
└── memoizedState (链表头)
│
▼ Node1: { memoizedState: 0 } ← useState(0)
│ next ──────────────────────────────────┐
▼ Node2: { memoizedState: '' } ← useState('')
│ next ──────────────────────────────────┐
▼ Node3: { memoizedState: false } ← useState(false)
next: null

简化版实现(核心逻辑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const fiber = { type: Counter, memoizedState: null };
let currentHook = null;

function useState(initialValue) {
if (!currentHook) {
const hook = { memoizedState: initialValue, next: null };
// 挂到 fiber.memoizedState 链表...
currentHook = hook;
}
const hook = currentHook;
currentHook = hook.next;

const setState = (newValue) => {
hook.memoizedState = newValue; // 直接改链表节点
scheduleReRender();
};
return [hook.memoizedState, setState];
}

// 每次渲染前重置为链表头
function renderComponent(Component) {
currentHook = fiber.memoizedState;
return Component();
}

两次渲染的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
首次渲染:
useState(0) → 创建 Node1 { memoizedState: 0 } 返回 [0, setCount]
useState('') → 创建 Node2 { memoizedState: '' } 返回 ['', setName]
useState(false)→ 创建 Node3 { memoizedState: false } 返回 [false, setFlag]

用户点击,setCount(1):
Node1.memoizedState = 1
scheduleReRender()

第二次渲染:
currentHook = fiber.memoizedState (Node1)
useState(0) → 读 Node1,返回 [1, setCount] ✓
useState('') → 读 Node2,返回 ['', setName] ✓
useState(false)→ 读 Node3,返回 [false, setFlag] ✓
调用顺序 Hook 对应链表节点 存的值
1 useState(0) Node1 0 → 1
2 useState(‘’) Node2 ‘’
3 useState(false) Node3 false

结论:状态不在组件函数里,而在该组件对应 Fiber 的 memoizedState 链表上,每个 Hook 按顺序对应链表中的一个节点。

1.3 为什么顺序不能变?

  • 第 1 次调用 Hook → 用链表第 1 个节点
  • 第 2 次调用 Hook → 用链表第 2 个节点
  • 顺序变了,就错位了

因此 Rules of Hooks 要求:顶层调用、不在循环/条件中。

1.4 各 Hook 的简化实现

useState:

1
2
3
4
5
6
7
8
9
10
11
12
13
let hooks = [];
let currentHookIndex = 0;

function useState(initialValue) {
const index = currentHookIndex;
const state = hooks[index] !== undefined ? hooks[index] : initialValue;
const setState = (newValue) => {
hooks[index] = newValue;
scheduleReRender();
};
currentHookIndex++;
return [state, setState];
}

useEffect: 将 callback 和 deps 存入链表,在 commit 阶段执行;依赖变化时重新执行。

useCallback / useMemo: 缓存上一次的值/函数,依赖数组变化才重新计算/创建。

1.4.1 useEffect 的三种情况

写法 何时执行 常见用途
useEffect(fn) 每次渲染后 很少用
useEffect(fn, []) 仅首次挂载后 订阅、初始化、只跑一次
useEffect(fn, [a, b]) a 或 b 变化时 依赖变化时执行副作用

实现思路:React 在链表节点上存「上次的 callback」和「上次的 deps」,每次渲染时比较 deps(用 Object.is):

  • 无 deps → 每次都执行
  • 空数组 [] → 首次执行(prevDeps 为空)
  • 有 deps → 逐项比较,任一变则执行
1
2
3
4
5
// 简化实现
let shouldRun = false;
if (!deps) shouldRun = true; // 无 deps:每次都执行
else if (!prevDeps) shouldRun = true; // 首次挂载
else shouldRun = deps.some((d, i) => !Object.is(d, prevDeps[i]));

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 无 deps:每次渲染后执行
useEffect(() => { console.log('每次渲染都执行'); });

// 2. 空 deps:仅首次
useEffect(() => {
console.log('只执行一次');
return () => console.log('卸载时执行');
}, []);

// 3. 有 deps:依赖变化时执行
useEffect(() => {
console.log('count 或 name 变化时执行');
}, [count, name]);
情况 依赖比较 是否执行
无 deps 不比较 每次
[] 首次 prevDeps 为空 只执行一次
[a,b] 用 Object.is 逐项比较 a 或 b 变化时才执行

1.5 小结

概念 本质
状态存储 存在 Fiber 上的链表,不在函数内部
Hook 识别 靠调用顺序对应到链表节点
Rules of Hooks 保证顺序和数量恒定,才能一一对应

1.6 Hooks 的实现本质

Hooks 的实现本质可归纳为三点:

  1. 外部存储:状态不存于组件函数内部,而是存在 Fiber 的 memoizedState 链表上。组件每次渲染都会重新执行,但链表在组件实例的生命周期内持续存在。

  2. 顺序索引:每次调用 Hook 时,React 通过一个「当前索引」决定读写哪个链表节点。索引随调用递增,因此必须保证每次渲染的调用顺序、数量完全一致。

  3. 调度更新:setState 等更新函数会修改链表上的值,并触发 React 的调度器安排一次重渲染。下次渲染时,组件函数重新执行,Hooks 按相同顺序从链表中读出最新值。

三者结合:外部存储解决「函数无状态」问题,顺序索引解决「多个 Hook 如何区分」问题,调度更新解决「变化如何驱动重渲染」问题。

1.7 闭包机制与 Hooks 的关系

Hooks 与闭包紧密相关:

1. setState 依赖闭包捕获索引

1
2
3
4
const setState = (newValue) => {
hooks[index] = newValue; // index 来自闭包
scheduleReRender();
};

setState 在首次渲染时创建,通过闭包捕获了当时的 index。即使用户在 3 秒后点击按钮调用 setState,它仍然能正确写入对应节点,因为闭包保留了 index。

2. stale closure(陈旧闭包)问题

事件回调、useEffect 中的函数会闭包捕获当次渲染时的 state。若在回调中直接使用 state,可能拿到旧值:

1
2
3
4
5
6
7
8
9
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 被闭包捕获,一直是 0
}, 1000);
return () => clearInterval(id);
}, []);
}

3. 正确做法:函数式更新

1
setCount(prev => prev + 1);  // 传入函数,React 注入最新 state

React 会传入最新的 state,避免闭包捕获 stale 值。

4. useCallback / useMemo 与闭包

useCallback(fn, deps) 缓存的是函数本身;若 deps 不变,返回的是同一个函数引用,其闭包捕获的也是旧依赖。useMemo 同理。因此 deps 必须完整列出闭包中用到的所有变量。

1.8 详细分析:一次更新的完整流程

以 useState 为例,从点击按钮到界面更新:

1
2
3
4
5
6
7
1. 用户点击 → 触发 onClick → 调用 setState(newValue)
2. setState 通过闭包中的 index 找到链表节点,写入新值
3. 调用 scheduleReRender(),React 将本次更新加入调度队列
4. 调度器在合适时机执行 render 阶段
5. 组件函数重新执行,useState(initialValue) 再次被调用
6. currentHookIndex 从 0 开始,按顺序遍历:hooks[index] 已有新值,返回 [newValue, setState]
7. 组件用新 state 生成新 JSX,进入 commit 阶段,DOM 更新

闭包保证了:异步回调中的 setState,在任意时刻被调用时,仍能通过 index 找到正确的链表节点。 链表保证了:多次渲染之间,状态得以保留。 两者缺一不可。


二、Fiber 的核心原理

2.1 一句话

Fiber 把「一次性递归完整个树」的同步更新,拆成「按节点逐步执行、可中断」的增量更新,让渲染不卡顿主线程。

2.2 为什么需要 Fiber?

旧版 (Stack Reconciler) Fiber 之后
从根递归整棵树,一气做完 拆成一个个小单元
无法暂停 可随时暂停,让出主线程
大树会长时间占用主线程 支持时间分片、优先级调度

2.3 Fiber 是什么?

Fiber = 对应一个 React 元素的「工作单元」,携带该节点的类型、props、子节点引用等。

1
2
3
4
5
6
7
8
9
Fiber {
type, key, props
child: 第一个子 Fiber
sibling: 下一个兄弟 Fiber
return: 父 Fiber(用于回溯)
alternate: 另一棵树的对应 Fiber(双缓冲)
flags: 增/删/改等标记
lane: 优先级
}

2.4 链表式遍历(而非递归)

树被转成链表结构,遍历时可随时停下、下次从断点继续:

1
2
3
4
5
6
7
        App
/ \
Header Main
/ \ |
Nav Logo Content

遍历顺序:App → Header → Nav → Logo → Main → Content ...

2.5 双缓冲 (alternate)

  • current:当前屏幕上的树
  • workInProgress:正在构建的新树

更新时在 workInProgress 上增量构建,commit 阶段一次性切换,避免闪烁。

2.6 总结

问题 回答
Fiber 是什么? 一个工作单元,带 child/sibling/return 的链表节点
为什么用? 实现可中断、可恢复、可调度
如何可中断? 链表遍历 + 时间分片
双缓冲? 两棵树交替,保证更新可复用且一次性提交

三、Fiber 作为一种设计思想

3.1 Fiber 可抽象为通用思想

思想 含义
可中断的增量工作 大任务拆成小单元,做一点可停
时间分片 固定时间片内工作,到点让出主线程
优先级调度 高优任务(如用户输入)插队
链表式遍历 用 child/sibling/return 实现可暂停、可恢复

这些思想可迁移到 UI 渲染、动画、大数据处理等场景。

3.2 Flutter 能否引入?

可以。Flutter 已具备类似机制:

Flutter 机制 对应思想
SchedulerBinding 分阶段调度(microtask、frame、idle)
优先级队列 高优任务优先执行
Isolate 重计算放到后台,减轻 UI 线程压力

若要在 layout 等环节做「增量可中断」,可借鉴 Fiber 的拆解与调度方式。

3.3 通用模式

1
2
3
4
5
1. 把大任务拆成小工作单元
2. 每个单元可单独执行、可暂停
3. 用链表/树记录进度,便于恢复
4. 调度器按优先级和时间片决定执行哪些
5. 高优任务可插队

四、整体逻辑关系

1
2
3
4
5
6
7
8
9
Hooks 底层
→ 状态存在 Fiber 的链表上,靠调用顺序对应
→ 闭包捕获索引,保证 setState 等更新函数能正确写入;需注意 stale closure

Fiber 核心
→ 把递归更新改为可中断的链表遍历 + 时间分片

Fiber 思想
→ 可中断、可调度、增量工作,具有普适性,可迁移到其他框架(如 Flutter)与场景

React Hooks

从基本概念到源码实现,全面解析 React Hooks


一、基本概念

1.1 什么是 Hooks?

Hooks 是 React 16.8 引入的新特性,它允许你在不编写 class 的情况下使用 state 以及其他的 React 特性。

简单来说,Hooks 就是让你在函数组件中”钩入” React 特性的函数。

1.2 为什么需要 Hooks?

在 Hooks 出现之前,React 存在以下问题:

痛点 描述
状态逻辑难复用 使用 render props 和 HOC 会导致组件嵌套地狱
复杂组件难理解 生命周期中充斥大量无关逻辑,难以拆分
Class 的学习成本 需要理解 this、绑定事件、生命周期等概念
逻辑分散 相关逻辑散落在 componentDidMount、componentDidUpdate、componentWillUnmount 中

1.3 核心 Hooks 一览

Hook 作用
useState 在函数组件中添加 state
useEffect 执行副作用(订阅、请求、DOM 操作等)
useContext 读取 Context 值
useReducer useState 的替代方案,适合复杂 state 逻辑
useCallback 缓存回调函数
useMemo 缓存计算结果
useRef 引用 DOM 或保存可变值
useLayoutEffect 同步执行副作用,在 DOM 更新后、浏览器绘制前

二、常用 Hooks 详解与示例

2.1 useState

1
2
3
4
5
6
7
8
9
10
11
12
13
function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* 函数式更新,适合依赖前一个 state */}
<button onClick={() => setCount(prev => prev + 1)}>+1 (函数式)</button>
</div>
);
}

关键点:

  • useState 接收初始值,返回 [当前值, 更新函数]
  • 初始值可以是函数(惰性初始化),用于避免每次渲染都执行 expensive 计算
1
const [state, setState] = useState(() => computeExpensiveInitialState());

2.2 useEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function DataFetcher({ id }) {
const [data, setData] = useState(null);

useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${id}`, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(console.error);

// 清理函数:组件卸载或 effect 重新执行前调用
return () => controller.abort();
}, [id]); // 依赖数组:id 变化时重新执行

return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

依赖数组规则:

  • 不传:每次渲染后都执行
  • []:仅挂载时执行一次
  • [a, b]:a 或 b 变化时执行

2.3 useRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function TextInput() {
const inputRef = useRef(null);

const focusInput = () => {
inputRef.current?.focus();
};

return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>聚焦</button>
</>
);
}

// 保存可变值,不触发重渲染
function Timer() {
const countRef = useRef(0);
countRef.current++; // 修改不会触发渲染
return <div>Render count: {countRef.current}</div>;
}

2.4 useCallback 与 useMemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');

// 缓存函数,避免子组件因引用变化而重渲染
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);

// 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(count);
}, [count]);

return <MemoChild onClick={handleClick} value={expensiveValue} />;
}

三、Hooks 原理浅析

3.1 核心思想:链表存储

React 在内部用链表来存储 Hooks。每个函数组件对应一个 Fiber 节点,每个 Hook 是链表上的一个节点。

1
2
3
4
5
Fiber 节点
└── memoizedState (第一个 Hook)
├── next -> (第二个 Hook)
│ ├── next -> (第三个 Hook)
│ │ └── ...

为什么是链表?

  • 函数组件没有 this,无法像 Class 那样用实例存储
  • 链表可以按调用顺序保存多个 Hook
  • 每次渲染时按顺序遍历链表,对应到正确的 state

3.2 两个重要的链表

React 内部维护两套 Hook 链表:

  1. current:当前屏幕上显示的 UI 对应的 Hooks
  2. workInProgress:正在构建的新的 Hooks

渲染流程大致为:

1
2
3
mount -> workInProgress 构建新链表
update -> 从 current 复制到 workInProgress,按顺序更新
commit -> workInProgress 替换 current

3.3 为什么 Hooks 必须在顶层调用?

1
2
3
4
5
6
7
// ❌ 错误:条件调用
function Bad() {
if (condition) {
const [a, setA] = useState(0); // 破坏调用顺序!
}
const [b, setB] = useState(0);
}

React 依赖 Hooks 的调用顺序 来将 state 与正确的 Hook 关联。如果顺序变化,会错位导致 bug。


四、源码解读

4.1 简化的 Hook 数据结构

1
2
3
4
5
6
7
8
// React 内部简化版
type Hook = {
memoizedState: any, // 当前 state 值
baseState: any, // 基础 state
baseQueue: Update | null,
queue: UpdateQueue, // 更新队列
next: Hook | null, // 下一个 Hook(链表)
};

4.2 useState 实现思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 极简版 useState 实现(理解原理用)
let hooks = [];
let currentHook = 0;

function useState(initialState) {
const index = currentHook;
hooks[index] = hooks[index] ?? (typeof initialState === 'function' ? initialState() : initialState);

function setState(newState) {
hooks[index] = typeof newState === 'function' ? newState(hooks[index]) : newState;
scheduleReRender();
}

currentHook++;
return [hooks[index], setState];
}

function useMyComponent() {
currentHook = 0;
const [count, setCount] = useState(0);
const [name, setName] = useState(''); // 按顺序压入链表
// ...
}

4.3 useEffect 实现思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function useEffect(callback, deps) {
const index = currentHook;
const prevDeps = hooks[index];

const hasChanged = !prevDeps || deps?.some((d, i) => d !== prevDeps[i]);

if (hasChanged) {
// 先执行上一次的 cleanup
if (typeof prevEffect?.cleanup === 'function') {
prevEffect.cleanup();
}
const cleanup = callback();
hooks[index] = { deps, cleanup };
}
currentHook++;
}

五、自定义 Hook

5.1 封装业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// useLocalStorage - 同步 state 到 localStorage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (e) {
return initialValue;
}
});

const setValue = useCallback((value) => {
setStoredValue(prev => {
const next = typeof value === 'function' ? value(prev) : value;
window.localStorage.setItem(key, JSON.stringify(next));
return next;
});
}, [key]);

return [storedValue, setValue];
}

// 使用
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>{theme}</button>;
}

5.2 useDebounce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);

return debouncedValue;
}

// 搜索防抖
function SearchInput() {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);

useEffect(() => {
if (!debouncedSearch) return;
fetchResults(debouncedSearch);
}, [debouncedSearch]);
// ...
}

5.3 useFetch(数据请求)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
setLoading(true);
const controller = new AbortController();

fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));

return () => controller.abort();
}, [url]);

return { data, loading, error };
}

六、实际项目应用案例

6.1 场景一:表单管理(复杂表单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});

const handleChange = useCallback((name) => (e) => {
setValues(prev => ({ ...prev, [name]: e.target.value }));
}, []);

const validate = useCallback(() => {
const newErrors = {};
if (!values.email) newErrors.email = '必填';
if (!values.password) newErrors.password = '必填';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}, [values]);

const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
}, []);

return { values, errors, handleChange, validate, reset };
}

// 登录表单
function LoginForm() {
const { values, errors, handleChange, validate } = useForm({ email: '', password: '' });

const onSubmit = (e) => {
e.preventDefault();
if (!validate()) return;
login(values);
};

return (
<form onSubmit={onSubmit}>
<input value={values.email} onChange={handleChange('email')} />
{errors.email && <span>{errors.email}</span>}
<input type="password" value={values.password} onChange={handleChange('password')} />
{errors.password && <span>{errors.password}</span>}
<button type="submit">登录</button>
</form>
);
}

6.2 场景二:无限滚动列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function useInfiniteScroll(callback, hasMore, loading) {
const observerRef = useRef();
const lastElementRef = useCallback(node => {
if (loading) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
callback();
}
});
if (node) observerRef.current.observe(node);
}, [loading, hasMore, callback]);

return lastElementRef;
}

function InfiniteList() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);

const loadMore = useCallback(async () => {
setLoading(true);
const res = await fetchItems(page);
setItems(prev => [...prev, ...res.data]);
setHasMore(res.hasMore);
setPage(p => p + 1);
setLoading(false);
}, [page]);

const lastRef = useInfiniteScroll(loadMore, hasMore, loading);

return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
<li ref={lastRef} style={{ height: 1 }} />
</ul>
);
}

6.3 场景三:权限与路由守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
authApi.getCurrentUser()
.then(setUser)
.finally(() => setLoading(false));
}, []);

const hasPermission = useCallback((permission) => {
return user?.permissions?.includes(permission);
}, [user]);

return { user, loading, hasPermission };
}

function ProtectedRoute({ permission, children }) {
const { user, loading, hasPermission } = useAuth();

if (loading) return <Spin />;
if (!user) return <Navigate to="/login" />;
if (permission && !hasPermission(permission)) return <Forbidden />;

return children;
}

6.4 场景四:性能优化(虚拟列表 + useMemo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function VirtualList({ items, itemHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef();

const visibleRange = useMemo(() => {
const start = Math.floor(scrollTop / itemHeight);
const end = start + Math.ceil(containerRef.current?.clientHeight / itemHeight) + 2;
return [Math.max(0, start), Math.min(items.length, end)];
}, [scrollTop, itemHeight, items.length]);

const visibleItems = useMemo(() => {
const [start, end] = visibleRange;
return items.slice(start, end).map((item, i) => ({
...item,
index: start + i,
}));
}, [items, visibleRange]);

return (
<div
ref={containerRef}
onScroll={e => setScrollTop(e.target.scrollTop)}
style={{ height: '100%', overflow: 'auto' }}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
{visibleItems.map(item => (
<div key={item.id} style={{ position: 'absolute', top: item.index * itemHeight }}>
{item.content}
</div>
))}
</div>
</div>
);
}

七、常见陷阱与最佳实践

7.1 闭包陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 错误:setTimeout 中拿到的是旧 count
function Bad() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 永远是 0
}, 1000);
return () => clearInterval(id);
}, []); // 缺少 count 依赖
}

// ✅ 正确:使用函数式更新
function Good() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
}

7.2 依赖数组的完整性

  • 建议使用 eslint-plugin-react-hooks 的 exhaustive-deps 规则
  • 不要随意省略依赖,可能导致 stale closure

7.3 何时用 useCallback / useMemo

  • 需要传递回调给 memo 子组件时用 useCallback
  • 计算成本高且依赖稳定时用 useMemo
  • 不要过度使用,有时重新创建的开销小于记忆化

八、总结

主题 要点
概念 Hooks 让函数组件拥有 state 和副作用能力
原理 链表存储,依赖调用顺序,current/workInProgress 双缓冲
源码 极简实现可帮助理解,真实实现见 React 仓库
自定义 抽取可复用逻辑,命名以 use 开头
实践 避免闭包陷阱,合理使用依赖数组,按需优化

React Hooks 是现代 React 开发的核心,掌握其原理与最佳实践,能显著提升代码质量与开发效率。


参考资料