Flutter Provider原理以及用法

63 阅读4分钟

Provider 是 Flutter 官方推荐的状态管理方案之一。它的核心原理是依赖 InheritedWidget 实现数据在组件树中的高效向下传递,同时结合观察者模式,让数据变化时,只有依赖该数据的 UI 部分才会自动更新,从而实现了性能和开发体验的平衡

核心原理

Provider 本质是对 Flutter 底层 InheritedWidget 的封装。

  1. 数据传递Provider 组件将其持有的数据存储在 InheritedWidget 中,这使得组件树下任何一个子组件都能通过 context 访问到这份数据,无需手动层层传递
  2. 响应式更新:当数据模型(通常混入 ChangeNotifier)发生变化时,它会调用 notifyListeners() 方法发出通知。Provider 监听到通知后,会强制刷新依赖该数据的 Consumer 或 Selector 等组件,从而实现 UI 的自动更新

核心组件介绍

要上手 Provider,主要就是熟悉下面这三个角色:

组件类型作用
ChangeNotifier数据模型 (Model)一个简单的类,来自 Flutter 原生 SDK。你通常需要 with ChangeNotifier 来为你的数据模型混入“通知”能力。当数据改变时,调用 notifyListeners()通知
ChangeNotifierProvider提供者 (Provider)顶级或上游的 Widget。它的 create 方法创建并持有数据模型的实例,并将其提供给整个子树
Consumer消费者 (Consumer)下游的 Widget。它声明自己需要监听哪个类型的数据模型,当该模型调用 notifyListeners() 时,Consumer 的 builder 方法会被执行,只重建自己这部分 UI

基础用法四步走

接下来,用最经典的计数器示例,带你走完完整流程。

第 1 步:添加依赖

在你的 pubspec.yaml 文件中添加 provider 依赖,然后运行 flutter pub get

dependencies:
  flutter:
    sdk: flutter
  provider: ^# 6.1.5+1 # 建议使用最新版本

(最新版本号请以 pub.dev 为准)

第 2 步:创建数据模型(Model)

创建一个类来管理状态,并使用 ChangeNotifier这是唯一需要你编写业务逻辑的地方

import 'package:flutter/material.dart';

// 1. 混入 (with) ChangeNotifier
class Counter with ChangeNotifier {
  int _count = 0;

  // 提供一个 getter 让外部获取状态,但无法直接修改
  int get count => _count;

  // 提供修改状态的方法
  void increment() {
    _count++;
    // 2. 数据改变后,调用 notifyListeners() 通知所有监听者
    notifyListeners();
  }
}

第 3 步:通过 Provider 提供数据(Provide)

在应用或页面的顶层,使用 ChangeNotifierProvider 包裹你的根组件,将数据模型“注入”到组件树中

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    // 将 ChangeNotifierProvider 置于顶层
    ChangeNotifierProvider(
      // create 方法负责创建模型实例
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

第 4 步:在组件中消费数据(Consume)

在需要访问数据和触发更新的子组件中,使用 Consumer 或 Provider.of 来获取模型实例

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider Demo')),
      body: Center(
        // 使用 Consumer 来监听 Counter 的变化
        // 泛型 <Counter> 指明了要监听的模型类型
        child: Consumer<Counter>(
          builder: (context, counter, child) {
            // builder 参数:context, 模型实例, 和可选的子widget
            return Text(
              'You have pushed the button this many times: ${counter.count}',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 获取模型实例,listen: false 表示只调用方法,不监听变化
          final counter = Provider.of<Counter>(context, listen: false);
          counter.increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

💡 进阶用法与最佳实践

  1. Consumer vs Provider.of

    • Consumer:推荐在 build 方法中使用。它能精确控制重建的范围,避免不必要的 UI 刷新
    • Provider.of<T>(context, listen: false)仅用于触发方法,比如按钮的 onPressed 回调中。设置 listen: false 可以避免因模型重建而触发该 Widget 重建。
    • Provider.of<T>(context) (等同于 context.watch<T>()):会监听变化,应谨慎使用,避免导致父组件大范围重建。
  2. 管理多个 Provider
    当需要管理多个数据模型时,使用 MultiProvider 可以让代码结构更清晰

    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => CartModel()),
        ChangeNotifierProvider(create: (context) => UserModel()),
        Provider(create: (context) => SomeService()), // 其他不需要通知的服务
      ],
      child: MyApp(),
    )
    
  3. 性能优化:Selector
    如果模型中有多个属性,而某个 Widget 只关心其中一个,使用 Selector 可以做到按需刷新,进一步提升性能

    Selector<Counter, int>(
      selector: (context, counter) => counter.count, // 只取出 count
      builder: (context, count, child) {
        return Text('$count');
      },
    )
    
  4. 处理依赖关系:ProxyProvider
    当一个 Model 需要依赖另一个 Model 时,比如 UserModel 依赖 SettingsModel,可以使用 ProxyProvider 来解决,ProxyProvider 会自动监听被依赖的 Model,当依赖更新时,自动更新当前 Model,实现状态联动。

4-1. 核心作用

  • A Model 需要使用 B Model 的数据 / 方法时
  • B 更新 时,A 能自动感知并更新
  • 替代手动在两个 Model 之间互相传参、监听、更新的麻烦代码

4-1. 常用类型

.  **ProxyProvider<A, B>** :B 依赖 A
.  **ProxyProvider2<A, B, C>** :C 依赖 A + B
.  最多支持 **ProxyProvider6**(依赖 6 个模型)

⚠️ 注意事项与常见问题

  • 获取不到 Provider? :使用 Provider.of<T>(context) 时,确保当前 context 的祖先节点中存在 ChangeNotifierProvider<T>。通常把 Provider 放在 MaterialApp 之上即可
  • 忘记调用 notifyListeners() :这是最常见的 Bug。修改完数据模型的属性后,务必调用 notifyListeners(),否则 UI 不会更新
  • build 方法频繁执行:检查是否在 build 方法中直接使用了 Provider.of<T>(context) 而没有设置 listen: false,或者 Consumer 放置的位置太高,重建范围过大。