Flutter路由传参详解

目录


一、路由传参的基本概念

路由传参是指在Flutter应用中,从一个页面(Widget)导航到另一个页面时,传递数据或参数的过程。

核心原理

  • Flutter的路由系统基于NavigatorRoute
  • 参数传递通过RouteSettings对象实现
  • 目标页面通过ModalRoute.of(context).settings.arguments获取参数

二、路由传参的几种方式

1. 构造函数传参

原理:直接通过目标页面的构造函数传递参数。

实现方式

1
2
3
4
5
6
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(data: 'Hello World'),
),
);

参数获取

1
2
3
4
5
6
7
8
9
10
11
12
class DetailScreen extends StatelessWidget {
final String data;

const DetailScreen({Key? key, required this.data}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text(data)),
);
}
}

2. RouteSettings传参

原理:通过RouteSettingsarguments属性传递参数。

实现方式

1
2
3
4
5
6
7
8
9
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: {'id': 1, 'name': 'Flutter'},
),
builder: (context) => DetailScreen(),
),
);

参数获取

1
2
3
4
5
6
@override
void didChangeDependencies() {
super.didChangeDependencies();
final args = ModalRoute.of(context)?.settings.arguments as Map;
// 处理参数
}

3. 命名路由传参

原理:通过命名路由的arguments参数传递数据。

实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注册路由
MaterialApp(
routes: {
'/detail': (context) => DetailScreen(),
},
);

// 导航并传参
Navigator.pushNamed(
context,
'/detail',
arguments: {'id': 1, 'name': 'Flutter'},
);

参数获取:与RouteSettings传参相同。

4. onGenerateRoute传参

原理:通过onGenerateRoute回调函数处理路由和参数。

实现方式

1
2
3
4
5
6
7
8
9
10
11
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments;
return MaterialPageRoute(
builder: (context) => DetailScreen(args: args),
);
}
return null;
},
);

参数获取:通过构造函数获取。

5. 全局状态管理传参

原理:使用Provider、Riverpod等状态管理库传递参数。

实现方式

1
2
3
4
5
6
7
8
9
10
11
// 定义状态
final userProvider = StateProvider<User>((ref) => User());

// 设置状态
context.read(userProvider).state = User(id: 1, name: 'Flutter');

// 导航
Navigator.pushNamed(context, '/detail');

// 获取状态
final user = context.watch(userProvider).state;

三、各种传参方式的比较

传参方式 优点 缺点 适用场景
构造函数传参 1. 类型安全 2. 代码清晰 3. 直接获取 1. 不支持命名路由 2. 参数变更不会触发重建 简单页面,参数较少
RouteSettings传参 1. 支持命名路由 2. 灵活传递各种类型 1. 类型不安全 2. 需要类型转换 复杂参数,命名路由
命名路由传参 1. 路由集中管理 2. 代码简洁 1. 类型不安全 2. 参数获取复杂 应用内页面导航
onGenerateRoute传参 1. 集中处理路由逻辑 2. 支持类型安全 1. 代码复杂度增加 2. 配置繁琐 大型应用,复杂路由
全局状态管理 1. 跨页面共享 2. 实时更新 3. 类型安全 1. 增加依赖 2. 过度设计风险 复杂状态,多页面共享

四、最佳实践

1. 根据场景选择传参方式

  • 简单参数:使用构造函数传参
  • 复杂参数:使用RouteSettings或onGenerateRoute
  • 跨页面共享:使用状态管理

2. 参数获取的最佳时机

不推荐:在initState()中直接获取参数

  • 原因:路由参数可能还未传递到Widget中

推荐

  1. 构造函数传参:直接使用构造函数参数
  2. RouteSettings传参:在didChangeDependencies()中获取
  3. 状态管理:在build()方法中通过watch获取

3. 类型安全

推荐使用类型化的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ScreenArguments {
final int id;
final String name;

ScreenArguments(this.id, this.name);
}

// 传递
Navigator.pushNamed(
context,
'/detail',
arguments: ScreenArguments(1, 'Flutter'),
);

// 获取
final args = ModalRoute.of(context)?.settings.arguments as ScreenArguments;

4. 性能优化

  • 避免在build方法中获取参数:可能导致重复获取
  • 使用didChangeDependencies:只在依赖变化时调用
  • 合理使用状态管理:避免过度使用

五、代码示例

示例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
// 导航页面
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(
id: 1,
title: 'Flutter Demo',
),
),
);
},
child: Text('Go to Detail'),
),
),
);
}
}

// 目标页面
class DetailScreen extends StatelessWidget {
final int id;
final String title;

const DetailScreen({Key? key, required this.id, required this.title})
: super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Text('ID: $id'),
),
);
}
}

示例2:RouteSettings传参

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
// 导航页面
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: {'id': 1, 'title': 'Flutter Demo'},
),
builder: (context) => DetailScreen(),
),
);

// 目标页面
class DetailScreen extends StatefulWidget {
@override
_DetailScreenState createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
late Map<String, dynamic> _args;

@override
void didChangeDependencies() {
super.didChangeDependencies();
_args = ModalRoute.of(context)?.settings.arguments as Map;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_args['title'])),
body: Center(
child: Text('ID: ${_args['id']}'),
),
);
}
}

示例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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 注册路由
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/detail': (context) => DetailScreen(),
},
);

// 导航
Navigator.pushNamed(
context,
'/detail',
arguments: ScreenArguments(1, 'Flutter Demo'),
);

// 目标页面
class DetailScreen extends StatefulWidget {
@override
_DetailScreenState createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
late ScreenArguments _args;

@override
void didChangeDependencies() {
super.didChangeDependencies();
_args = ModalRoute.of(context)?.settings.arguments as ScreenArguments;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_args.title)),
body: Center(
child: Text('ID: ${_args.id}'),
),
);
}
}

class ScreenArguments {
final int id;
final String title;

ScreenArguments(this.id, this.title);
}

示例4:onGenerateRoute传参

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
// 配置路由
MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case '/detail':
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) => DetailScreen(args: args),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
},
);

// 导航
Navigator.pushNamed(
context,
'/detail',
arguments: ScreenArguments(1, 'Flutter Demo'),
);

// 目标页面
class DetailScreen extends StatelessWidget {
final ScreenArguments args;

const DetailScreen({Key? key, required this.args}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(args.title)),
body: Center(
child: Text('ID: ${args.id}'),
),
);
}
}

示例5:全局状态管理传参

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
// 定义状态
final userProvider = StateProvider<User>((ref) => User());

// 设置状态
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
context.read(userProvider).state = User(id: 1, name: 'Flutter');
Navigator.pushNamed(context, '/detail');
},
child: Text('Go to Detail'),
),
),
);
}
}

// 获取状态
class DetailScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider).state;
return Scaffold(
appBar: AppBar(title: Text(user.name)),
body: Center(
child: Text('ID: ${user.id}'),
),
);
}
}

class User {
final int id;
final String name;

User({this.id = 0, this.name = ''});
}

总结

Flutter提供了多种路由传参方式,每种方式都有其适用场景:

  1. 构造函数传参:简单直接,类型安全,适用于简单页面
  2. RouteSettings传参:灵活多样,支持命名路由,适用于复杂参数
  3. 命名路由传参:集中管理,代码简洁,适用于应用内导航
  4. onGenerateRoute传参:集中处理,支持类型安全,适用于大型应用
  5. 全局状态管理:跨页面共享,实时更新,适用于复杂状态

选择合适的传参方式需要考虑:

  • 参数的复杂度和类型
  • 页面间的关系
  • 代码的可维护性
  • 性能要求

通过合理选择和组合使用这些方式,可以构建出清晰、高效的Flutter应用导航系统。

Flutter 路由详解

目录


一、路由基础概念

1. 什么是路由?

路由(Route)是指应用中页面之间的导航机制,在Flutter中,路由是由Navigator组件管理的Route对象的堆栈。

2. 核心组件

  • Navigator:管理路由堆栈的核心类,负责页面的推入、弹出和替换
  • Route:表示一个页面的抽象类,常用实现有MaterialPageRouteCupertinoPageRoute
  • RouteSettings:包含路由的配置信息,如路由名称和参数

3. 路由堆栈

  • 推入(push):将新页面添加到堆栈顶部
  • 弹出(pop):移除堆栈顶部的页面
  • 替换(replace):用新页面替换堆栈顶部的页面
  • 清空(popUntil):弹出页面直到指定条件

二、基本导航方式

1. 基本导航

推入新页面

1
2
3
4
5
6
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);

返回上一页

1
Navigator.pop(context);

带返回值的导航

1
2
3
4
5
6
7
8
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectionScreen(),
),
);
// 处理返回结果
print('Result: $result');

2. 替换路由

1
2
3
4
5
6
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => NewScreen(),
),
);

3. 移除指定路由

1
2
3
4
Navigator.popUntil(
context,
ModalRoute.withName('/home'),
);

4. 清空堆栈并导航

1
2
3
4
5
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
(Route<dynamic> route) => false, // 移除所有路由
);

三、路由传参详解

1. 构造函数传参

传递参数

1
2
3
4
5
6
7
8
9
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(
id: 1,
title: 'Flutter Demo',
),
),
);

接收参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DetailScreen extends StatelessWidget {
final int id;
final String title;

const DetailScreen({Key? key, required this.id, required this.title})
: super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('ID: $id')),
);
}
}

2. RouteSettings传参

传递参数

1
2
3
4
5
6
7
8
9
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: {'id': 1, 'title': 'Flutter Demo'},
),
builder: (context) => DetailScreen(),
),
);

接收参数

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

class _DetailScreenState extends State<DetailScreen> {
late Map<String, dynamic> _args;

@override
void didChangeDependencies() {
super.didChangeDependencies();
_args = ModalRoute.of(context)?.settings.arguments as Map;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_args['title'])),
body: Center(child: Text('ID: ${_args['id']}')),
);
}
}

3. 类型安全的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ScreenArguments {
final int id;
final String title;

ScreenArguments(this.id, this.title);
}

// 传递
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: ScreenArguments(1, 'Flutter Demo'),
),
builder: (context) => DetailScreen(),
),
);

// 接收
final args = ModalRoute.of(context)?.settings.arguments as ScreenArguments;

四、命名路由管理

1. 基本配置

1
2
3
4
5
6
7
8
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/detail': (context) => DetailScreen(),
},
);

2. 导航到命名路由

1
Navigator.pushNamed(context, '/second');

3. 带参数的命名路由

1
2
3
4
5
Navigator.pushNamed(
context,
'/detail',
arguments: ScreenArguments(1, 'Flutter Demo'),
);

4. onGenerateRoute

用于处理未在routes中注册的路由,或需要动态生成的路由:

1
2
3
4
5
6
7
8
9
10
11
12
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) => DetailScreen(args: args),
);
}
// 处理其他路由...
return MaterialPageRoute(builder: (context) => NotFoundScreen());
},
);

5. onUnknownRoute

onGenerateRoute无法处理路由时调用:

1
2
3
4
5
MaterialApp(
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => NotFoundScreen());
},
);

五、路由守卫与拦截

1. 自定义路由拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class AuthGuard extends StatelessWidget {
final Widget child;

const AuthGuard({Key? key, required this.child}) : super(key: key);

@override
Widget build(BuildContext context) {
final isLoggedIn = AuthService.isLoggedIn();

if (!isLoggedIn) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
});
}

return child;
}
}

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
41
42
43
44
45
class RouteObserverExample extends StatefulWidget {
@override
_RouteObserverExampleState createState() => _RouteObserverExampleState();
}

class _RouteObserverExampleState extends State<RouteObserverExample> with RouteAware {
static final routeObserver = RouteObserver<PageRoute>();

@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
}

@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}

@override
void didPush() {
// 路由被推入时调用
print('Route pushed');
}

@override
void didPop() {
// 路由被弹出时调用
print('Route popped');
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Route Observer')),
body: Center(child: Text('Route Observer Example')),
);
}
}

// 在MaterialApp中注册
MaterialApp(
navigatorObservers: [RouteObserverExample.routeObserver],
);

六、深度链接与通用链接

1. 配置深度链接

Android配置 (AndroidManifest.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="example.com"
android:pathPrefix="/detail" />
</intent-filter>
</activity>

iOS配置 (Info.plist):

1
2
3
4
5
6
7
8
9
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

2. 处理深度链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name?.startsWith('/detail/') ?? false) {
// 解析路径参数
final pathSegments = Uri.parse(settings.name!).pathSegments;
final id = pathSegments[1];
return MaterialPageRoute(
builder: (context) => DetailScreen(id: id),
);
}
// 其他路由处理...
},
);

3. 使用uni_links库

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
import 'package:uni_links/uni_links.dart';

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

class _MyAppState extends State<MyApp> {
StreamSubscription? _linkSubscription;

@override
void initState() {
super.initState();
_initDeepLinks();
}

void _initDeepLinks() async {
// 处理初始链接
final initialLink = await getInitialLink();
_handleDeepLink(initialLink);

// 监听后续链接
_linkSubscription = linkStream.listen((String? link) {
_handleDeepLink(link);
});
}

void _handleDeepLink(String? link) {
if (link != null) {
final uri = Uri.parse(link);
if (uri.path.startsWith('/detail')) {
final id = uri.queryParameters['id'];
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(id: id)),
);
}
}
}

@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
);
}
}

七、路由动画与过渡效果

1. 自定义页面过渡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;

var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);

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
// 淡入淡出
Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
),
);

// 缩放
Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: animation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
),
);

3. 平台特定过渡

1
2
3
4
5
6
7
8
9
10
11
// Android风格
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen()),
);

// iOS风格
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => DetailScreen()),
);

八、嵌套路由与Tab导航

1. 嵌套Navigator

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 HomeScreen extends StatelessWidget {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Navigator(
key: _navigatorKey,
initialRoute: '/home',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/home':
return MaterialPageRoute(builder: (context) => HomeContent());
case '/home/detail':
return MaterialPageRoute(builder: (context) => HomeDetail());
default:
return null;
}
},
),
);
}
}

2. Tab导航与路由

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
class TabNavigator extends StatelessWidget {
final String tabItem;
final GlobalKey<NavigatorState> navigatorKey;

const TabNavigator({
Key? key,
required this.tabItem,
required this.navigatorKey,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
Widget child;
switch (settings.name) {
case '/':
child = getTabScreen(tabItem);
break;
case '/detail':
child = DetailScreen();
break;
default:
child = getTabScreen(tabItem);
}
return MaterialPageRoute(builder: (context) => child);
},
);
}

Widget getTabScreen(String tabItem) {
switch (tabItem) {
case 'home':
return HomeScreen();
case 'profile':
return ProfileScreen();
default:
return HomeScreen();
}
}
}

class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
String _currentTab = 'home';
final Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
'home': GlobalKey<NavigatorState>(),
'profile': GlobalKey<NavigatorState>(),
};

void _selectTab(String tabItem) {
if (tabItem == _currentTab) {
_navigatorKeys[tabItem]?.currentState?.popUntil((route) => route.isFirst);
} else {
setState(() => _currentTab = tabItem);
}
}

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab = !await _navigatorKeys[_currentTab]!.currentState!.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentTab != 'home') {
_selectTab('home');
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
body: Stack(children: [
_buildOffstageNavigator('home'),
_buildOffstageNavigator('profile'),
]),
bottomNavigationBar: BottomNavigationBar(
currentIndex: ['home', 'profile'].indexOf(_currentTab),
onTap: (index) => _selectTab(['home', 'profile'][index]),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}

Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentTab != tabItem,
child: TabNavigator(
tabItem: tabItem,
navigatorKey: _navigatorKeys[tabItem]!,
),
);
}
}

九、路由性能优化

1. 延迟加载路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/heavy') {
return MaterialPageRoute(builder: (context) {
// 延迟加载重量级页面
return FutureBuilder(
future: Future.delayed(Duration(milliseconds: 100)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return HeavyScreen();
}
return Center(child: CircularProgressIndicator());
},
);
});
}
// 其他路由...
},
);

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
class PreloadManager {
static final Map<String, Widget> _preloadedWidgets = {};

static Future<void> preload(String routeName) async {
switch (routeName) {
case '/detail':
_preloadedWidgets['/detail'] = DetailScreen();
break;
case '/settings':
_preloadedWidgets['/settings'] = SettingsScreen();
break;
}
}

static Widget? getPreloaded(String routeName) {
return _preloadedWidgets[routeName];
}
}

// 使用
await PreloadManager.preload('/detail');

// 导航时使用预加载的Widget
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PreloadManager.getPreloaded('/detail')!),
);

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
27
28
29
class RouteCache {
static final Map<String, Widget> _cache = {};

static Widget getOrCreate(String routeName, Widget Function() creator) {
if (!_cache.containsKey(routeName)) {
_cache[routeName] = creator();
}
return _cache[routeName]!;
}

static void clear() {
_cache.clear();
}

static void remove(String routeName) {
_cache.remove(routeName);
}
}

// 使用
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteCache.getOrCreate(
'/detail',
() => DetailScreen(),
),
),
);

十、最佳实践

1. 路由管理架构

推荐的路由管理方式

  • 使用命名路由进行集中管理
  • 结合onGenerateRoute处理动态路由
  • 使用类型安全的参数传递

目录结构

1
2
3
4
5
6
7
8
9
10
lib/
├── routes/
│ ├── route_names.dart # 路由名称常量
│ ├── route_arguments.dart # 路由参数类型
│ └── route_generator.dart # 路由生成器
├── screens/
│ ├── home_screen.dart
│ ├── detail_screen.dart
│ └── ...
└── main.dart

2. 路由名称管理

1
2
3
4
5
6
7
// route_names.dart
class RouteNames {
static const String home = '/';
static const String detail = '/detail';
static const String settings = '/settings';
static const String profile = '/profile';
}

3. 路由参数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
// route_arguments.dart
class DetailArguments {
final int id;
final String title;

DetailArguments(this.id, this.title);
}

class ProfileArguments {
final String userId;

ProfileArguments(this.userId);
}

4. 路由生成器

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
// route_generator.dart
import 'package:flutter/material.dart';
import 'package:your_app/screens/home_screen.dart';
import 'package:your_app/screens/detail_screen.dart';
import 'package:your_app/screens/settings_screen.dart';
import 'package:your_app/screens/profile_screen.dart';
import 'package:your_app/routes/route_names.dart';
import 'package:your_app/routes/route_arguments.dart';

class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RouteNames.home:
return MaterialPageRoute(builder: (_) => HomeScreen());

case RouteNames.detail:
final args = settings.arguments as DetailArguments;
return MaterialPageRoute(
builder: (_) => DetailScreen(args: args),
);

case RouteNames.settings:
return MaterialPageRoute(builder: (_) => SettingsScreen());

case RouteNames.profile:
final args = settings.arguments as ProfileArguments;
return MaterialPageRoute(
builder: (_) => ProfileScreen(args: args),
);

default:
return _errorRoute();
}
}

static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(title: Text('Error')),
body: Center(child: Text('Page not found')),
);
});
}
}

5. 应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// main.dart
import 'package:flutter/material.dart';
import 'package:your_app/routes/route_generator.dart';
import 'package:your_app/routes/route_names.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Route Demo',
initialRoute: RouteNames.home,
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}

6. 导航工具类

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
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

static Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushNamed(routeName, arguments: arguments);
}

static void goBack() {
navigatorKey.currentState!.pop();
}

static Future<dynamic> replaceWith(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushReplacementNamed(routeName, arguments: arguments);
}

static void popUntil(String routeName) {
navigatorKey.currentState!.popUntil(ModalRoute.withName(routeName));
}

static Future<dynamic> pushAndRemoveUntil(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushNamedAndRemoveUntil(
routeName,
(Route<dynamic> route) => false,
arguments: arguments,
);
}
}

// 配置
MaterialApp(
navigatorKey: NavigationService.navigatorKey,
// ...
);

// 使用
NavigationService.navigateTo(RouteNames.detail, arguments: DetailArguments(1, 'Flutter'));

总结

Flutter的路由系统是一个强大而灵活的导航框架,通过掌握以下核心概念和技术,可以构建出流畅、高效的应用导航体验:

  1. 基础导航:掌握Navigator的基本操作
  2. 参数传递:选择合适的传参方式
  3. 命名路由:实现集中化的路由管理
  4. 路由守卫:处理权限和导航逻辑
  5. 深度链接:支持外部链接打开应用
  6. 路由动画:提升用户体验
  7. 嵌套路由:实现复杂的导航结构
  8. 性能优化:确保导航流畅性

通过合理的路由设计和管理,可以显著提升应用的用户体验和代码可维护性。希望这份指南能帮助你更好地理解和使用Flutter的路由系统。

Flutter 到区块链:分层架构与交互全流程

本文从移动端工程视角,说明 Flutter + 原生 + 密码学库(如 Trust Wallet Core)+ 节点 RPC + 区块链 在用户完成一笔链上交易时的典型分工与数据流。适用于自托管钱包、DEX 集成等场景的架构理解(具体产品实现可能有网关、风控等差异)。

1. 先厘清三件事

  1. 区块链节点不会直接和你的手机 App「长连接握手」完成业务;App 通常通过 HTTPS 上的 JSON-RPC(或 REST)远程节点通信。
  2. Trust Wallet Core 一类库,主体多为 C++,负责 地址派生、交易构造、数字签名、按链协议序列化不负责替你选公共节点或发起 HTTP(由宿主 App 实现)。
  3. Flutter 负责 UI 与业务状态;涉及 私钥、签名 时,常见做法是放到 iOS/Android 原生(Keychain / Keystore + JNI),再通过 MethodChannel 与 Dart 通信。

2. 分层总览:谁做什么

层级 典型技术 职责
表现层 Flutter Widget 表单、确认弹窗、加载态、结果页
业务 / 状态 Bloc / Riverpod / Provider 等 参数校验、组装请求模型、调用 Channel
跨端桥 MethodChannel / Pigeon / FFI Dart ↔ 原生类型与异步传递
原生 Kotlin / Swift 安全存储、调 TWC 绑定、HTTP 客户端、证书策略
密码学 / 链协议 Trust Wallet Core(C++ .so / xcframework) 未签名交易 → 签名 → 原始交易字节 / hex
网络 OkHttp / URLSession / Dio RPC:读 nonce、余额、estimateGas;写 eth_sendRawTransaction
公链节点集群 验证交易、广播、mempool、出块

核心结论:「上链」在工程上 = 库内完成签名 + 网络层把已签名交易发给节点


3. 架构图:从界面到链

flowchart TB
  subgraph UI["表现层"]
    F["Flutter Widget\n按钮、金额、链选择、确认弹窗"]
  end

  subgraph VM["业务层"]
    B["Flutter 业务逻辑\n校验、状态机、组装 DTO"]
  end

  subgraph Bridge["跨端桥"]
    C["MethodChannel / Pigeon"]
  end

  subgraph Native["原生层"]
    N1["Kotlin / Swift\nKeychain / Keystore、权限"]
    N2["Trust Wallet Core\n(C++ 动态库)\n交易体 + 签名"]
  end

  subgraph Net["网络层"]
    H["HTTP 客户端"]
    RPC["节点 RPC\nJSON-RPC / REST"]
  end

  subgraph Chain["区块链"]
    BC["共识与出块"]
  end

  F --> B --> C --> N1 --> N2
  N2 -->|"signed tx"| N1
  N1 --> H --> RPC --> BC

4. 主流程时序图:用户点击「确认」之后

以下为 签名在原生、RPC 也由原生(或统一网络层)发起 的常见形态,便于统一钉证书与审计日志。

sequenceDiagram
  autonumber
  participant U as 用户
  participant W as Flutter UI
  participant B as Flutter 业务层
  participant CH as MethodChannel
  participant N as 原生
  participant TWC as Trust Wallet Core
  participant RPC as 节点 RPC
  participant BC as 区块链

  U->>W: 点击确认交易
  W->>B: 提交 to / amount / chainId 等
  B->>B: 校验与组装参数

  B->>CH: invokeMethod(signAndSend / buildTx, args)
  CH->>N: 传递到原生

  N->>N: 解锁密钥材料(生物识别 / PIN)
  N->>RPC: 读链:nonce、fee、余额等
  RPC-->>N: 返回值

  N->>TWC: 构造未签名交易并签名
  TWC-->>N: 已签名交易 hex / bytes

  N->>RPC: 写链:eth_sendRawTransaction 等
  RPC->>BC: 广播
  BC-->>RPC: 返回 tx hash
  RPC-->>N: tx hash

  N-->>CH: { txHash, error? }
  CH-->>B: Future 完成
  B-->>W: 更新 UI
  W-->>U: 展示哈希 / 区块浏览器

步骤解读(与上图序号对应)

  1. 用户操作:仅在 UI 层。
  2. 业务层:做产品规则校验(限额、格式),不碰私钥明文。
  3. Channel:把「意图」和「非敏感参数」交给原生;不要把私钥字符串在 Dart 里传来传去
  4. 原生:从安全存储取密钥或触发系统认证。
  5. 读链 RPC:例如 EVM 链上需要先拿 nonce、估算 gas;这些请求 与签名无关,走 HTTP 即可。
  6. TWC:输入链类型、账户、目标交易内容,输出 签名后的原始交易
  7. 写链 RPC:将 已签名交易 发给节点;节点再进入 mempool。
  8. :矿工/验证者打包,产生确认数。

5. 变体:原生只签名,Flutter 发 RPC

部分团队会让 Dart 层用 Dio 直接调 Infura / Alchemy / 自建节点,原生 MethodChannel 只返回 signedHex

sequenceDiagram
  participant B as Flutter
  participant CH as MethodChannel
  participant N as 原生
  participant TWC as Trust Wallet Core
  participant RPC as 节点

  B->>CH: signOnly(unsignedTxParams)
  CH->>N: 
  N->>TWC: 签名
  TWC-->>N: signed hex
  N-->>CH: 
  CH-->>B: signed hex

  B->>RPC: eth_sendRawTransaction(signedHex)
  RPC-->>B: tx hash

注意:私钥仍应只在原生 + TWC 边界内使用;Dart 侧只拿 已签名 的十六进制串,不要把助记词传到 Flutter。


6. 数据流简图:字节往哪走

flowchart LR
  subgraph in["输入"]
    A1["用户输入\n地址、金额"]
    A2["链与合约参数\nchainId、ABI、nonce"]
  end

  subgraph twc["Trust Wallet Core"]
    S["编码 + 签名\n→ raw transaction"]
  end

  subgraph out["对外"]
    H["0x… 已签名交易"]
    TX["交易哈希"]
  end

  A1 --> S
  A2 --> S
  S --> H
  H -->|"HTTPS JSON-RPC"| RPC["节点"]
  RPC --> TX

7. Trust Wallet Core 与「和链交互」的关系

  • 开发语言:核心为 C++;Android 为 libTrustWalletCore.so,iOS 为 xcframework;通过 Kotlin / Swift 绑定 调用,而非在 App 内用 JS 实现核心链逻辑。
  • 和链的交互方式
    • 库内部:密码学、交易结构、签名算法、各链差异。
    • 库外部:你的 App 用 RPCsigned transaction只读请求(余额、nonce)发到节点;协议多为 JSON-RPC(如以太坊系)。

因此:「与区块链交互」在实现上 = RPC 上的读写TWC 负责产生可被链接受的那串字节


8. 安全与工程要点

  • 私钥与助记词:优先 Keychain / Keystore / 硬件模块;Dart 内存尽量不出现明文。
  • Channel 传参:传 交易参数、链 ID、签名结果,不传私钥。
  • RPC 端点:生产环境常配合 证书钉扎、代理、自有网关,防止中间人篡改 sendRawTransaction 的返回。
  • 重放与链切换:正确设置 chainId(EIP-155),避免跨链重放。

9. 与「交易所托管」模式的对比(扩展)

若业务是 CEX 托管钱包,时序里往往在 用户确认 之后增加 你们服务端:风控、限额、内部记账,再由服务端或专用签名机与链交互。此时上图中的 RPC 可能变为 先调业务 API,与纯 自托管 + TWC 的图不同。理解分层后,可按实际产品替换「RPC 前」的一跳。


10. 小结

问题 答案
Flutter 怎么「上链」? 多数情况下 不直接上链,而是 调原生签名 + HTTP 调节点 RPC
TWC 做什么? 交易构造与签名(C++ 库)。
链从哪里来? 远程节点返回区块与收据;广播即节点接受你的 sendRawTransaction
JS 是不是核心? 不是;可选 WASM/TS 绑定,核心仍是 C++。

文档为通用架构说明,便于学习与工程对照;具体 App 的网关、风控、多签等需以实际系统为准。

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 对应链表中的一个节点,通过 FibermemoizedState 串联。

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)在合适的时机批量处理,触发 rendercommit

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 等重建
}
}

ProviderInheritedProvideraddListenerChangeNotifier,当 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 ToolkitZustand,服务端状态用 React Query 等。
  • Flutter:从 setState 起步,共享状态用 Provider 入门,进阶用 RiverpodBloc
  • 选型时关注:团队熟悉度项目规模可测试性与框架生态的契合度

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