Flutter 状态管理入门:从 setState 到 ChangeNotifier(我自己的上手路线)

Flutter 的“状态管理”一开始看起来玄乎,学着学着又发现:其实就是一句话——数据变了,界面要跟着变

我刚入门的时候最容易卡住的点,不是写不出代码,而是不知道“这一行状态应该放哪、该用什么方式更新”。这篇我就按自己的使用习惯,把入门阶段最常见的几条路捋一遍:从 setState 开始,过渡到 ValueNotifier,最后到 ChangeNotifier + Provider 这种更适合项目组织的写法。


1)先把“状态”讲人话

在我理解里,Flutter 的 state 就两类:

  • 短命状态:只在某个页面/组件里用(输入框内容、选中项、展开/收起)
  • 长命状态:跨页面、跨模块共享(登录信息、购物车、主题/语言、全局配置)

短命状态很多时候 setState 就够了;长命状态一多,才需要更系统的管理方式。


2)第一站:setState(别嫌它土,真香)

如果你只是做一个页面的小交互,setState 是最省心的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CounterPage extends StatefulWidget {
const CounterPage({super.key});

@override
State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
int count = 0;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(child: Text('count = $count')),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => count++),
child: const Icon(Icons.add),
),
);
}
}

我对 setState 的使用边界也很明确:

  • 页面内部状态 → 用它
  • 一旦要跨组件共享、或者逻辑开始变复杂 → 立刻换工具

3)第二站:ValueNotifier(轻量、好用、我用得很多)

ValueNotifier 适合那种“一个值驱动一块 UI”的场景,比 setState 更干净一些,尤其适合把状态从 widget 里挪出来一点点,但又不想引入整套状态管理框架。

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 LikeButton extends StatefulWidget {
const LikeButton({super.key});

@override
State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
final liked = ValueNotifier(false);

@override
void dispose() {
liked.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: liked,
builder: (context, value, _) {
return IconButton(
onPressed: () => liked.value = !value,
icon: Icon(value ? Icons.favorite : Icons.favorite_border),
);
},
);
}
}

它的好处是:你不会把整个页面都 setState 一遍,而是把重建范围锁在 ValueListenableBuilder 里。


4)第三站:ChangeNotifier(当你开始“管理一坨状态”)

当状态不再是一个值,而是一组数据 + 一些业务操作(比如登录、购物车、个人资料),我会直接用 ChangeNotifier 把这些东西“收进一个类里”。

1
2
3
4
5
6
7
8
9
10
11
12
13
class CartModel extends ChangeNotifier {
final List<String> items = [];

void add(String id) {
items.add(id);
notifyListeners();
}

void remove(String id) {
items.remove(id);
notifyListeners();
}
}

到这一步,你已经在做两件很重要的事:

  • 把 UI 和业务逻辑分开
  • 让状态可以被多个地方复用

5)把 ChangeNotifier 用起来:Provider(入门阶段很稳)

光有 ChangeNotifier 不够,你还需要一个“把它挂到 widget tree 上”的方式。入门阶段我最常用的是 provider(生态成熟、资料多、上手快)。

① 顶层注入

1
2
3
4
5
6
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
],
child: const MyApp(),
)

② 在页面里读取/监听

1
2
3
4
5
6
// 只读一次(不监听刷新)
final cart = context.read<CartModel>();
cart.add('p001');

// 监听变化(变了就刷新)
final items = context.watch<CartModel>().items;

如果你不想让整个页面跟着刷新,可以用 ConsumerSelector 把刷新范围缩小:

1
2
3
Consumer<CartModel>(
builder: (_, cart, __) => Text('items: ${cart.items.length}'),
)

6)我怎么选:别一上来就“宇宙级方案”

我自己的选择顺序大概是:

  • 只是一个页面的小状态 → setState
  • 一个值驱动局部 UI → ValueNotifier
  • 一组状态 + 一堆操作要复用 → ChangeNotifier
  • 需要跨页面共享、组织项目 → ChangeNotifier + Provider

你会发现:这条路线其实就是“从简单到可维护”。
别急着追最炫的框架,先把项目写顺,后面再换也不迟。


结尾

状态管理说到底不是背 API,而是做取舍:把状态放在离它“最近、最合理”的地方
你现在如果正在写一个具体页面(比如登录页、购物车、个人中心),把结构发我,我可以按你的业务给一个更贴近实际的拆分建议:哪些用 setState,哪些上 ChangeNotifier,哪些该做成全局。

Share