Provider与setState哪个开销更高?MVVM架构下用Provider替代setState是否合理?
1. Provider与setState二者哪个开销更高?
默认场景下setState的基础开销远低于Provider。setState是Flutter原生的局部刷新API,仅会触发当前StatefulWidget及其子树的重建,逻辑链路非常短,没有额外的分发开销。而Provider的状态更新基于InheritedWidget的通知机制,需要先遍历上下文找到对应的Provider节点,再匹配所有注册了该状态监听的组件触发重建,本身多了状态分发、依赖校验的逻辑,基础开销更高。
但实际使用的开销不能只看API本身:如果你用setState的时候每次都刷新整个页面的大子树,而Provider通过精确控制只刷新单个需要更新的文本组件,那实际场景下Provider的开销反而会更低,核心取决于你刷新的范围大小。
2. 用Provider替代setState是否属于良好的开发实践?
完全用Provider替代setState不属于良好实践,属于过度设计。
setState本身就是官方提供的本地状态管理方案,对于仅在单个组件内部生效的状态(比如开关的选中状态、输入框的临时输入内容、组件内的动画进度),直接用setState是最简洁、最高效的实现,完全没必要把这些无关全局的状态塞到Provider里,只会增加代码的维护成本。
只有当状态需要跨组件/跨页面共享(比如用户登录态、全局主题配置、跨页共享的购物车数据),或者MVVM架构下需要把业务逻辑从视图层抽离的时候,用Provider才是合理的,能很好的解耦业务和视图,也方便状态复用。
3. Provider会不会重建整个应用的Widget树?如何更高效使用Provider?
Provider本身不会默认重建整个应用的Widget树,只有监听了被修改的对应状态的组件才会被触发重建。比如你在第二个页面修改了UserProvider的用户名,第一个页面没有任何组件监听这个UserProvider的用户名变化,那第一个页面完全不会被重建。
绝大多数的全量重建问题都是错误的使用姿势导致的,比如直接在页面根组件的build方法里调用Provider.of<T>(context, listen: true),导致整个页面被标记为需要重建,或者状态类没有实现不可变,导致每次状态更新都触发所有监听者重建。
高效使用Provider的实操规则:
- 优先用
Selector代替Consumer,明确指定你需要监听的状态字段,只有该字段变化时才触发重建,避免无关状态更新导致的无效刷新:
// 仅监听UserModel中的userName字段,其余字段更新不会触发当前组件重建 Selector<UserModel, String>( selector: (context, userModel) => userModel.userName, builder: (context, userName, child) => Text(userName), )
- 使用
Consumer/Selector时,将不需要随状态刷新的组件传入child参数,这部分组件只会被构建一次,不会随状态更新重复构建 - 所有状态类都设计为不可变,内部字段全部用
final修饰,更新状态时返回全新的状态对象,不要直接修改原有状态的字段值 - 按状态作用域拆分Provider,把Provider放在尽可能靠近使用它的组件的位置,不要把所有Provider都塞到应用根节点;仅在单个页面内共享的状态就放在页面的根节点,不需要放到全局
- 组件内部的私有状态优先用setState管理,不要全部放到Provider里
内容的提问来源于stack exchange,提问作者Deepak Lohmod

