Flutter 状态管理:Provider 入门与实战

在 Flutter 应用开发中,状态管理是构建复杂应用的核心挑战之一。Provider 作为官方推荐的状态管理方案,以其简洁的 API 和强大的功能,成为众多开发者的首选。本文将深入探讨 Provider 的原理、使用方法和高级技巧,帮助你掌握这一重要的状态管理工具。

Provider 介绍与原理

核心原理:

  • 依赖注入 : Provider 将状态(例如一个数据对象或服务)注入到 Widget 树中。
  • 事件通知 : 当状态发生变化时,Provider 会通知所有"监听"了这个状态的 Widget。
  • 局部刷新 : 只有那些真正关心状态变化的 Widget 会被重建(rebuild),从而避免了不必要的 UI 刷新,保证了应用的性能。

Provider 是一个轻量级的状态管理库, 本质上是一个基于 InheritedWidget 的封装,通过对InheritedWidget的一次工程化的封装把"暴露状态 → 订阅变化 → 自动清理/销毁"的流程做了统一抽象。当Provider在 Widget 树的顶层提供了某个数据(状态)后,其下方的任何子 Widget 都能方便地获取到这份数据,并在数据变化时得到通知,从而自动更新 UI。与直接使用InheritedWidget 相比,Provider 提供了更简洁的 API 和更丰富的功能,显著减少样板代码,配合context.watch/read/select、Consumer、Selector,能精准控制重建范围,提升性能与可维护性。并且它支持 lazy-loading(延迟创建)与自动 dispose,将业务逻辑与 UI 视图分离,使得代码结构更清晰,状态变更的流向也更加明确和可预测,是官方推荐的简单应用状态管理方案。

接下来我将通过具体的实例来介绍演示Provider各个功能的具体用法以及使用场景。

基础用法:

ChangeNotifierProvider 是 Provider 包中最常用的一个组件。它与 ChangeNotifier 结合使用,当 ChangeNotifier 对象的值发生变化并调用 notifyListeners() 方法时,所有监听它的 Widget 都会收到通知。

下面我们通过经典的计数器例子来演示其基本用法。

第一步:添加依赖

首先,在我们的 pubspec.yaml 文件中添加 Provider 依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.5 # 编写这篇文章时的最新版本

然后运行 flutter pub get 安装。

第二步:创建数据模型

创建一个继承自ChangeNotifier 的类。这个类将持有我们的状态(计数值),并在状态改变时通知监听者。

scala 复制代码
import 'package:flutter/foundation.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知监听者数据已更新
  }
}

第三步:提供数据模型

在你的应用顶层(或某个页面的顶层),使用 ChangeNotifierProvider 来创建并提供 CounterModel 实例。这样,其下的所有子 Widget 都能访问到这个实例。

typescript 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart'; // 引入我们创建的模型

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Provider 计数器示例'),
        ),
        body: const CounterBody(),
        floatingActionButton: const CounterButton(),
      ),
    );
  }
}

第四步:消费 (使用) 数据

在需要显示和修改计数器的 Widget 中,通过 context.watch 或 Consumer 来获取 CounterModel 并与之交互。

scala 复制代码
class CounterBody extends StatelessWidget {
  const CounterBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final count = context.watch<CounterModel>().count;
    return Center(
      child: Text(
        '当前计数值: $count',
        style: Theme.of(context).textTheme.headlineMedium,
      ),
    );
  }
}

class CounterButton extends StatelessWidget {
  const CounterButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        context.read<CounterModel>().increment();
      },
      child: const Icon(Icons.add),
    );
  }
}

这样,我们就实现了一个记录计数状态的计数器。

效果如下:

可以看到随着我不断点击右下方的FloatingActionButton,CounterModel实例中的_count数值不断加1,页面上的计数也随之不断进行累加。

值得注意的是默认情况下,Provider 的 create 和 update 回调是延迟调用 的。这意味着,只有当该 Provider 第一次被读取时,create 函数才会被执行。这个特性可以优化应用的启动性能,避免在启动时就创建所有可能用到的对象。如果你希望Provider在定义时就立即创建实例,可以将lazy 参数设置为false

less 复制代码
ChangeNotifierProvider(
  create: (context) => CounterModel(),
  lazy: false, // 设置为 false
  child: const MyApp(),
),

在大型应用中如果注入的内容较多,为了避免嵌套,可以使用**MultiProvider,**下面是官方文档中的例子:

不使用MultiProvider:

less 复制代码
Provider<Something>(
  create: (_) => Something(),
  child: Provider<SomethingElse>(
    create: (_) => SomethingElse(),
    child: Provider<AnotherThing>(
      create: (_) => AnotherThing(),
      child: someWidget,
    ),
  ),
),

使用MultiProvider:

scss 复制代码
MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)

多页面共享Provider实例

在复杂的应用中,我们常常需要在多个不同的页面(路由)之间共享同一个状态实例。例如,用户的登录信息、购物车内容等。

要实现这一点通常的做法是抬高作用域将 Provider 放置在所有需要共享该状态的页面的共同父节点之上。通常,这个位置是 MaterialApp 的上方。

下面是一个在两个页面共享用户登录状态的例子:

首先定义用户模型:

ini 复制代码
import 'package:flutter/foundation.dart';

class UserModel extends ChangeNotifier {
  String? _username;
  String? get username => _username;

  bool get isLoggedIn => _username != null;

  void login(String username) {
    _username = username;
    notifyListeners();
  }

  void logout() {
    _username = null;
    notifyListeners();
  }
}

HomePage代码:

less 复制代码
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 监听 UserModel 的变化
    final user = context.watch<UserModel>();

    return Scaffold(
      appBar: AppBar(title: const Text('主页')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(user.isLoggedIn
                ? '欢迎, ${user.username}!'
                : '您尚未登录'),
            const SizedBox(height: 20),
            if (!user.isLoggedIn)
              ElevatedButton(
                onPressed: () {
                  // 通过 read 调用登录方法
                  context.read<UserModel>().login('Raiden');
                },
                child: const Text('登录'),
              ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/profile');
              },
              child: const Text('前往个人资料页'),
            ),
          ],
        ),
      ),
    );
  }
}

ProfilePage代码:

less 复制代码
class ProfilePage extends StatelessWidget {
  const ProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 同样可以监听或读取 UserModel
    final user = context.watch<UserModel>();

    return Scaffold(
      appBar: AppBar(title: const Text('个人资料')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(user.isLoggedIn
                ? '用户名: ${user.username}'
                : '请先登录'),
            const SizedBox(height: 20),
            if (user.isLoggedIn)
              ElevatedButton(
                style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                onPressed: () {
                  context.read<UserModel>().logout();
                },
                child: const Text('退出登录'),
              ),
          ],
        ),
      ),
    );
  }
}

定义路由,实例化Provider:

typescript 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'user_model.dart'; // 引入模型

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => UserModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Multi-page Demo',
      // 定义路由
      initialRoute: '/',
      routes: {
        '/': (context) => const HomePage(),
        '/profile': (context) => const ProfilePage(),
      },
    );
  }
}

效果如下:

在这个例子中,由于 UserModel 的 Provider 在 MaterialApp 之上,所以由 MaterialApp 管理的所有路由(HomePage 和 ProfilePage)都可以访问到同一个 UserModel 实例。在一个页面上的登录或退出操作会立即反映在另一个页面上。

如果你希望集中控制共享范围,又只在多个指定页面之间共享模型,可考虑在导航时动态注入共享的Provider实例,或者对于创建好的实例进行手动传递以及局部提供的方法。

下面是一个简单的例子:

less 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Counter extends ChangeNotifier {
  int _value = 0;
  int get value => _value;
  void inc() {
    _value++;
    notifyListeners();
  }
}

// 单例/外部持有
final counter = Counter();

void main() => runApp(const Root());

class Root extends StatelessWidget {
  const Root({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 路由 A 与 B 分属不同入口处,各自用 .value 注入"同一个 counter"
      routes: {
        '/': (_) =>
            ChangeNotifierProvider.value(value: counter, child: const PageA()),
        '/b': (_) =>
            ChangeNotifierProvider.value(value: counter, child: const PageB()),
      },
    );
  }
}

class PageA extends StatelessWidget {
  const PageA({super.key});
  @override
  Widget build(BuildContext context) {
    final v = context.watch<Counter>().value;
    return Scaffold(
      appBar: AppBar(title: const Text('Page A')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('A: $v', style: TextStyle(fontSize: 25)),
            TextButton(
              onPressed: () {
                Navigator.of(context).pushNamed('/b');
              },
              child: Text("Page B", style: TextStyle(fontSize: 25)),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<Counter>().inc(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

class PageB extends StatelessWidget {
  const PageB({super.key});
  @override
  Widget build(BuildContext context) {
    final v = context.watch<Counter>().value;
    return Scaffold(
      appBar: AppBar(title: const Text('Page B')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('B: $v', style: TextStyle(fontSize: 25)),
            TextButton(
              onPressed: () {
                Navigator.of(context).pushNamed('/');
              },
              child: Text("Page A", style: TextStyle(fontSize: 25)),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<Counter>().inc(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

效果如下:

这里要注意,如果选择这个方案,那么需要开发者在使用这个实例时自己维护好这个实例的声明周期。

读取值的多种方式

Provider提供了多种读取值的方式,以应对不同的场景,实现更精细的性能控制。

首先是BuildContext的**扩展属性:**context.watch, context.read, context.select。

context.watch():

  • 作用:监听 T 类型的 Provider。当 T 发生变化(即调用了 notifyListeners())时,会导致调用此方法的 Widget 重建。
  • 适用场景:当我们需要在 UI 上显示某个会变化的数据时使用。例如,显示用户名、购物车商品数量等。

示例:

scss 复制代码
// 当 userModel 变化时,这个 Text Widget 会重建
Text('欢迎, ${context.watch<UserModel>().username}')

context.read():

  • 作用:仅读取一次 T 类型 Provider 的值,不会监听其后续变化,也不会在数据变化时触发 Widget 重建。
  • 适用场景:当你需要触发一个事件,而不是响应 UI 更新时使用。例如,在 onPressed 回调中调用一个方法。
  • 注意:不能在 StatelessWidget.build 以及State.build 方法中调用 context.read。

示例:

scss 复制代码
ElevatedButton(
  onPressed: () {
    // 只读取一次 UserModel 来调用 login 方法,不会导致此按钮重建
    context.read<UserModel>().login('Raiden');
  },
  child: const Text('登录'),
)

context.select<T,R>()(R cb(T value)):

  • 作用 :监听 T 类型 Provider 的一部分数据 R。只有当这部分数据 R 发生变化时,才会触发 Widget 重建。
  • 适用场景:当一个 Provider 包含多个状态,而我们的 Widget 只依赖其中一小部分时,使用 select 可以避免因不相关的状态变化而导致的无效重建,是性能优化的利器。

示例:假设 UserModel 还有一个 themeColor 属性。

ini 复制代码
class UserModel extends ChangeNotifier {
  String? _username;
  Color _themeColor = Colors.blue;

  String? get username => _username;
  Color get themeColor => _themeColor;

  void changeUsername(String name) {
    _username = name;
    notifyListeners();
  }

  void changeTheme() {
    _themeColor = _themeColor == Colors.blue ? Colors.red : Colors.blue;
    notifyListeners();
  }
}

// 这个 Widget 只关心用户是否登录,不关心主题颜色
final isLoggedIn = context.select<UserModel, bool>((user) => user.isLoggedIn);

// 即使主题颜色变化,这个 Text 也不会重建。只有当登录状态变化时才会重建。
return Text(isLoggedIn ? '已登录' : '未登录');

接着是Consumer/Selector:

ConsumerSelector 是以 Widget 形式存在的读取方式,它们的作用与 context.watch 和 context.select 类似,但能更好地控制重建范围。

Consumer :

  • 作用:等同于 context.watch() ,但它会将重建的范围精确控制在其 builder 函数内部。这对于将一个大 Widget 中只有一小部分依赖 Provider 的情况非常有用。

示例:

less 复制代码
// 假设这是一个复杂的、构建成本很高的 Widget
Scaffold(
  appBar: AppBar(title: const Text('Consumer Demo')),
  body: Column(
    children: [
      const Text('这个部分不会因为计数器变化而重建'),
      // 只有 Consumer 的 builder 会在 CounterModel 变化时重建
      Consumer<CounterModel>(
        builder: (context, counter, child) {
          // 'counter' 就是 CounterModel 的实例
          return Text('计数值: ${counter.count}');
        },
      ),
      const Text('这个部分也不会重建'),
    ],
  ),
)

Selector<T,R> :

  • 作用 :是 Consumer 和 context.select 的结合体。它允许你选择性地监听 Provider 的一部分数据,并且只重建其 builder 内部的 Widget。

示例:

javascript 复制代码
// 假设 UserModel 中有 username 和 age 两个属性
// 这个 Selector 只监听 username 的变化
Selector<UserModel, String>(
  // 第一个泛型是 Provider 类型,第二个是选择的数据类型
  selector: (context, user) => user.username ?? 'Guest',
  builder: (context, username, child) {
    // 只有当 username 变化时,这个 Text 才会重建
    return Text('Welcome, $username');
  },
)

这里要注意Selector 默认用 DeepCollectionEquality作比对并选取结果,所以必要时需要用shouldRebuild作比对。 关于builder中的child参数,这个参数是用来注入那些不依赖于Provider状态,无需在状态改变时重建的静态子组件。使用这个参数进行注入可以减少不必要的UI重建,提高性能。

下面是官网购物车案例中的示例代码:

less 复制代码
return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
    children: [
      // Use SomeExpensiveWidget here, without rebuilding every time.
      if (child != null) child,
      Text('Total price: ${cart.totalPrice}'),
    ],
  ),
  // Build the expensive widget here.
  child: const SomeExpensiveWidget(),
);

当CartModel调用notifyListeners()之后,只有包裹在builder内依赖cart状态的部分才会重建,而SomeExpensiveWidget则不会。

那我们在开发过程中应该如何进行选择?

  1. 想在 UI 中显示数据并跟随变化?

    • 如果整个 build 方法都依赖这个数据,用 context.watch()。
    • 如果只是 build 方法中的一小部分依赖数据,用 Consumer 来缩小重建范围。
    • 如果只依赖数据模型中的某个特定值,用 context.select 或 Selector 来实现最精细的性能控制。
  2. 只想触发一个动作(如点击按钮)?

    • 在事件回调函数(如 onPressed, onTap)中,永远使用 context.read()。

ProxyProvider:依赖其他 Provider 的 Provider

有时候,一个 Provider 的创建需要依赖另一个 Provider 的值。例如,一个订单服务可能需要依赖当前的用户信息。这时,ProxyProvider 就派上了用场。

ProxyProvider 会监听其他 Provider 的变化,并在其依赖的 Provider 更新时,重新创建或更新自己。

这里我简单举个例子,假设我们有一个 SettingsService 提供当前的语言设置,还有一个 TranslationService 需要根据这个语言设置来提供不同的翻译文本。

代码如下:

dart 复制代码
import 'package:flutter/material.dart';

// 设置服务
class SettingsService extends ChangeNotifier {
  String _languageCode = 'en'; // 默认英文
  String get languageCode => _languageCode;

  void setLanguage(String code) {
    _languageCode = code;
    notifyListeners();
  }
}

// 翻译服务 (依赖 SettingsService)
class TranslationService {
  final SettingsService _settings;

  TranslationService(this._settings) {
    print('TranslationService created for language: ${_settings.languageCode}');
  }

  String get greeting {
    if (_settings.languageCode == 'en') {
      return 'Hello!';
    } else if (_settings.languageCode == 'zh') {
      return '你好!';
    }
    return '...';
  }
}

创建Provider实例:

javascript 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// ... 引入上面的文件

void main() {
  runApp(
    MultiProvider(
      providers: [
        // 1. 先提供基础的 Provider: SettingsService
        ChangeNotifierProvider(create: (_) => SettingsService()),

        // 2. 再提供 ProxyProvider,它依赖于上面的 SettingsService
        ProxyProvider<SettingsService, TranslationService>(
          update: (context, settings, previous) => TranslationService(settings),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

这里说明一下update回调,update回调在 SettingsService 变化时被调用,'settings' 是最新的 SettingsService 实例,'previous' 是旧的 TranslationService 实例(如果有的话)。

在页面中使用:

less 复制代码
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('ProxyProvider Demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 读取翻译服务的问候语
              Consumer<TranslationService>(
                builder: (context, translation, child) {
                  return Text(
                    translation.greeting,
                    style: Theme.of(context).textTheme.headlineMedium,
                  );
                },
              ),
              const SizedBox(height: 20),
              // 读取设置服务来改变语言
              Consumer<SettingsService>(
                builder: (context, settings, child) {
                  return Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      ElevatedButton(
                        onPressed: () => settings.setLanguage('en'),
                        child: const Text('English'),
                      ),
                      const SizedBox(width: 10),
                      ElevatedButton(
                        onPressed: () => settings.setLanguage('zh'),
                        child: const Text('中文'),
                      ),
                    ],
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

当我们点击"中文"按钮时,SettingsService 会调用notifyListeners() 。ProxyProvider 监听到这个变化,会重新执行其 update 回调,并传入最新的 SettingsService 实例,从而创建一个新的 TranslationService 实例。最后,Consumer会获取到这个新的实例,并用新的问候语"你好!"来重建 Text Widget。

热重载 (Hot Reload) 时的对象处理

Provider 的 update 回调(存在于ProxyProvider 和 ChangeNotifierProxyProvider中)有一个非常特殊且重要的用途:在开发过程中的热重载期间保持状态

在 Flutter 开发中,热重载会重新执行 build 方法,但不会重新运行initState 或 main 函数。对于普通的 Provicer,create 回调在热重载时通常不会被再次调用,其持有的对象状态得以保留。

但是对于 ProxyProvider,它的 update 回调在热重载时会被再次调用 。这时,update 回调的第三个参数previous就显得至关重要了。previous 参数代表了热重载之前的那个对象实例。

这个功能做什么用的?

它允许我们在热重载后,将旧对象的状态迁移到新对象上,或者直接复用旧的对象,避免因热重载而丢失我们正在调试的状态。

什么时候可以用到?

  1. 调试复杂状态:当我们的 Provider 对象内部维护了一个复杂的状态(例如,一个多步骤表单的当前数据),我们希望在修改了 UI 代码并热重载后,这个状态依然存在。
  2. 保持资源连接:如果我们的 Provider 对象管理着一个需要长时间维持的资源连接(如 WebSocket 连接),我们可能不希望每次热重载都断开并重新连接。

消费接口并提供实现(抽象与实现分离)

Provider 非常适合用于实现依赖注入和接口驱动开发。我们可以让我们的 Widget 依赖于一个抽象的接口,而在应用的顶层通过 Provider 来提供这个接口的具体实现。

这个功能的用法及应用场景:

  • 解耦:UI 层代码不依赖于任何具体的服务实现,只知道接口定义的功能。这使得替换底层实现变得非常容易。
  • 可测试性:在进行单元测试或 Widget 测试时,你可以轻松地提供一个"模拟"(Mock)的实现来代替真实的服务(如网络请求、数据库操作),从而让测试更稳定、更快速。
  • 多环境配置:可以为开发、测试和生产环境提供不同的接口实现。例如,开发时使用连接本地服务器的ApiService , 生产环境则使用连接线上服务器的 ApiService。

下面我就以登录为例,写一个非常简单的例子:

首先是定义抽象接口:

arduino 复制代码
abstract class IAuthService {
  Future<String?> login(String username, String password);
  Future<void> logout();
}

提供具体的实现:

dart 复制代码
import 'auth_service_interface.dart';

// 一个用于开发的伪实现
class AuthService implements IAuthService {
  @override
  Future<String?> login(String username, String password) async {
    await Future.delayed(const Duration(seconds: 1));
    if (username == 'test' && password == '123') {
      return 'fake_user_token';
    }
    return null;
  }

  @override
  Future<void> logout() async {
    await Future.delayed(const Duration(milliseconds: 500));
  }
}

在应用中提供实现,并且消费接口:

scala 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'services/auth_service_interface.dart';
import 'services/fake_auth_service.dart';

void main() {
  runApp(
    Provider<IAuthService>( // 提供的是接口类型
      create: (_) => AuthService(), // 但创建的是具体实现
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: LoginPage());
  }
}

class LoginPage extends StatelessWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // Widget 中消费的是接口,它并不知道背后是 AuthService
            final authService = context.read<IAuthService>();
            final token = await authService.login('test', '123');
            print('Login result token: $token');
          },
          child: const Text('Login'),
        ),
      ),
    );
  }
}

通过这种方式,LoginPage 完全与 AuthService 解耦。如果未来我们需要对接真实的后端 API,只需创建一个 ApiAuthService 实现 IAuthService接口,然后在 main.dart 中替换 create 的返回对象即可,UI 层的代码完全不需要改动。

其他 Provider 类型

除了ChangeNotifierProvider和 ProxyProvider,provider 包还提供了一些处理特定异步场景的 Provider。这里就通过一张表格简单的说一下作用,就不展开细说了。

Provider 类型 作用
FutureProvider 接收一个 Future,并将其结果暴露给子孙 Widget。它会自动处理 Future 的不同状态(加载中、完成、错误),并通知消费者进行相应的 UI 更新。
StreamProvider 接收一个 Stream,并将其发出的最新数据暴露给子孙 Widget。每当 Stream 发出一个新事件时,消费者都会收到通知并更新。
ListenableProvider 一个更通用的版本,可以与任何实现了 Listenable 接口的对象(如 AnimationController)一起使用,而不仅限于 ChangeNotifier。

总结

Provider 无疑是 Flutter 生态中优秀且易上手的轻量级状态管理库之一。它以其优雅的设计和出色的性能,完美地融入了 Flutter 的声明式 UI 框架。

从上述内容中我们看到,Provider 不仅仅是一个简单的状态容器,它提供了一整套完善的工具链来应对各种开发场景:

  • 通过 ChangeNotifierProviderChangeNotifier,我们可以轻松实现响应式的数据模型。
  • 延迟加载机制确保了应用的启动性能。
  • 将 Provider 置于 Widget 树的高层,便能实现跨页面的状态共享
  • 利用 read , watch , select , Consumer , 和 Selector,我们可以对 Widget 的重建进行精细化控制,从而达到极致的性能优化。
  • ProxyProvider 优雅地解决了 Provider 之间的依赖关系问题。
  • 通过消费接口、提供实现的模式,我们可以构建出低耦合、高内聚、易于测试和维护的健壮应用。
  • FutureProviderStreamProvider 则为处理异步数据流提供了简洁高效的解决方案。

掌握 Provider,不仅仅是学会一个工具,更是深入理解 Flutter 框架数据流动和状态管理核心思想的过程。希望通过本文的介绍与实例,你能对 Provider 有一个全面而深刻的认识,并在未来的 Flutter 开发之旅中,利用它来构建出更加优秀的应用。

相关推荐
tangweiguo0305198710 分钟前
Dart语言“跨界”指南:从JavaScript到Kotlin,如何用多语言思维快速上手
flutter
pe7er13 分钟前
使用RealFaviconGenerator.net一站式生成各平台兼容 Favicon
前端
用户25191624271115 分钟前
Canvas之贪吃蛇
前端·javascript·canvas
一枚前端小能手17 分钟前
🔥 TypeScript高手都在用的4个类型黑科技
前端·typescript
Elieal21 分钟前
深入浅出:Ajax 与 Servlet 实现前后端数据交互
前端·ajax·servlet
王者鳜錸31 分钟前
VUE+SPRINGBOOT从0-1打造前后端-前后台系统-文章详情、评论、点赞
前端·vue.js·spring boot
乐予吕31 分钟前
别再乱用箭头函数了!JavaScript 三种函数写法的终极指南
前端·javascript·代码规范
雨绸缪32 分钟前
编程之路:我为什么要编程
前端·程序员
一大树36 分钟前
Vue 3 中 `ref` 的“浅监听”行为解析:是误解还是真相?
前端·vue.js
海天胜景37 分钟前
vue3 el-select 加载内容后 触发事件
前端·javascript·vue.js