跨平台状态管理: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

参考资源

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 开发的核心,掌握其原理与最佳实践,能显著提升代码质量与开发效率。


参考资料