Flutter Provider 模式实现:基于 InheritedWidget 的状态管理实现

发布于:2025-08-17 ⋅ 阅读:(14) ⋅ 点赞:(0)

本博客的参考文章:7.3 跨组件状态共享 | 《Flutter实战·第二版》

通过前一篇文章: Flutter InheritedWidget 详解:从生命周期到数据流动的完整解析,我们已经深入理解了 InheritedWidget 的工作原理,现在让我们基于这些知识来实现一个完整的 Provider 模式。这将帮助我们解决实际开发中的状态管理问题。

现有方案的问题

在前面的例子中,我们发现了一个性能问题:每次调用 setState() 时,整个组件树都会重建,即使某些子组件并不依赖变化的数据。

// 问题代码示例
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  
  Widget build(BuildContext context) {
    print("😱 整个页面都在重建!");
    return ShareDataWidget(
      data: count,
      child: Column(
        children: [
          TestWidget(),        // 依赖数据,需要更新
          StaticWidget(),      // 不依赖数据,但也被重建了!
          AnotherWidget(),     // 不依赖数据,但也被重建了!
        ],
      ),
    );
  }
}

解决方案:缓存 + Provider 模式

我们需要实现一个智能的状态管理方案,它应该具备以下特性:

  1. 精确更新:只更新真正依赖数据的组件
  2. 缓存机制:避免不必要的重建
  3. 易于使用:提供简洁的 API
  4. 类型安全:支持泛型,避免类型错误

实现 ChangeNotifier:可观察的数据模型

首先,我们需要一个可以通知变化的数据模型:

// 抽象的变化通知器
abstract class ChangeNotifier {
  List<VoidCallback> _listeners = [];

  // 添加监听器
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  // 移除监听器
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }

  // 通知所有监听器
  void notifyListeners() {
    for (final listener in _listeners) {
      listener();
    }
  }

  // 销毁时清理监听器
  void dispose() {
    _listeners.clear();
  }
}

// 具体的计数器模型
class CounterModel extends ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    print("📊 CounterModel: count 增加到 $_count,通知监听器");
    notifyListeners(); // 通知所有监听器数据已变化
  }
  
  void decrement() {
    _count--;
    print("📊 CounterModel: count 减少到 $_count,通知监听器");
    notifyListeners();
  }
}

实现 ChangeNotifierProvider:智能的数据提供者

class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final T Function() create;  // 创建数据模型的工厂函数
  final Widget child;
  final bool lazy;           // 是否懒加载

  const ChangeNotifierProvider({
    Key? key,
    required this.create,
    required this.child,
    this.lazy = true,
  }) : super(key: key);

  // 便捷方法:获取数据模型
  static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
    if (listen) {
      // 建立依赖关系,当数据变化时会重建
      return context.dependOnInheritedWidgetOfExactType<_InheritedProvider<T>>()!.model;
    } else {
      // 不建立依赖关系,仅获取数据
      return context.getElementForInheritedWidgetOfExactType<_InheritedProvider<T>>()!.widget.model;
    }
  }

  
  _ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}

class _ChangeNotifierProviderState<T extends ChangeNotifier> 
    extends State<ChangeNotifierProvider<T>> {
  
  T? _model;
  
  
  void initState() {
    super.initState();
    print("🏗️ ChangeNotifierProvider initState");
    
    if (!widget.lazy) {
      _createModel();
    }
  }
  
  void _createModel() {
    if (_model == null) {
      print("🔧 创建数据模型 ${T.toString()}");
      _model = widget.create();
      _model!.addListener(_onModelChanged);
    }
  }
  
  void _onModelChanged() {
    print("📢 数据模型变化,触发 setState");
    // 当模型数据变化时,重建 InheritedWidget
    setState(() {});
  }
  
  
  Widget build(BuildContext context) {
    print("🎨 ChangeNotifierProvider build");
    
    if (widget.lazy && _model == null) {
      _createModel();
    }
    
    return _InheritedProvider<T>(
      model: _model!,
      child: widget.child,
    );
  }
  
  
  void dispose() {
    print("🗑️ ChangeNotifierProvider dispose");
    _model?.removeListener(_onModelChanged);
    _model?.dispose();
    super.dispose();
  }
}

// 内部使用的 InheritedWidget
class _InheritedProvider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

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

  
  bool updateShouldNotify(_InheritedProvider<T> oldWidget) {
    // 这里我们总是返回 true,因为 ChangeNotifier 已经确保只有在真正变化时才会触发重建
    bool shouldNotify = oldWidget.model != model;
    print("⚖️ _InheritedProvider updateShouldNotify: $shouldNotify");
    return shouldNotify;
  }
}

实现 Consumer:智能的数据消费者

class Consumer<T extends ChangeNotifier> extends StatelessWidget {
  final Widget Function(BuildContext context, T model, Widget? child) builder;
  final Widget? child; // 不需要重建的子组件,用于性能优化

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

  
  Widget build(BuildContext context) {
    print("🎯 Consumer<${T.toString()}> build");
    
    // 获取数据模型并建立依赖关系
    final model = ChangeNotifierProvider.of<T>(context, listen: true);
    
    return builder(context, model, child);
  }
}

// 不监听变化的 Consumer,用于性能优化
class ConsumerWidget<T extends ChangeNotifier> extends StatelessWidget {
  final Widget Function(BuildContext context, T model) builder;

  const ConsumerWidget({
    Key? key,
    required this.builder,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    print("🎯 ConsumerWidget<${T.toString()}> build (不监听变化)");
    
    // 获取数据模型但不建立依赖关系
    final model = ChangeNotifierProvider.of<T>(context, listen: false);
    
    return builder(context, model);
  }
}

完整的使用示例

现在让我们用新的 Provider 模式重写计数器应用:

class ProviderDemoApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider 模式演示',
      home: ChangeNotifierProvider<CounterModel>(
        create: () => CounterModel(),
        child: CounterHomePage(),
      ),
    );
  }
}

class CounterHomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    print("🏠 CounterHomePage build - 这个页面不会因为计数变化而重建!");
    
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 计数器'),
      ),
      body: Column(
        children: [
          // 显示计数的组件 - 会监听变化
          CounterDisplay(),
          
          // 静态组件 - 不会重建
          StaticInfoWidget(),
          
          // 操作按钮区域
          CounterControls(),
          
          // 另一个显示计数的组件
          AnotherCounterDisplay(),
        ],
      ),
    );
  }
}

// 显示计数的组件 - 使用 Consumer 监听变化
class CounterDisplay extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Consumer<CounterModel>(
      builder: (context, counter, child) {
        print("🔢 CounterDisplay 重建,新的计数值: ${counter.count}");
        
        return Container(
          padding: EdgeInsets.all(20),
          margin: EdgeInsets.all(20),
          decoration: BoxDecoration(
            color: Colors.blue.shade100,
            borderRadius: BorderRadius.circular(10),
            border: Border.all(color: Colors.blue),
          ),
          child: Column(
            children: [
              Text(
                '当前计数',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              SizedBox(height: 10),
              Text(
                '${counter.count}',
                style: TextStyle(fontSize: 48, color: Colors.blue),
              ),
            ],
          ),
        );
      },
    );
  }
}

// 静态组件 - 不依赖数据,不会重建
class StaticInfoWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    print("ℹ️ StaticInfoWidget build - 我不会因为计数变化而重建");
    
    return Container(
      padding: EdgeInsets.all(15),
      margin: EdgeInsets.symmetric(horizontal: 20),
      decoration: BoxDecoration(
        color: Colors.grey.shade100,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        children: [
          Icon(Icons.info, color: Colors.grey),
          SizedBox(width: 10),
          Text(
            '这是一个静态组件,不会因为计数变化而重建',
            style: TextStyle(color: Colors.grey.shade700),
          ),
        ],
      ),
    );
  }
}

// 操作按钮组件
class CounterControls extends StatelessWidget {
  
  Widget build(BuildContext context) {
    print("🎮 CounterControls build");
    
    return Container(
      padding: EdgeInsets.all(20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          // 减少按钮 - 不监听变化,仅获取模型来调用方法
          ConsumerWidget<CounterModel>(
            builder: (context, counter) {
              return ElevatedButton.icon(
                onPressed: counter.decrement,
                icon: Icon(Icons.remove),
                label: Text('减少'),
                style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
              );
            },
          ),
          
          // 增加按钮 - 不监听变化,仅获取模型来调用方法
          ConsumerWidget<CounterModel>(
            builder: (context, counter) {
              return ElevatedButton.icon(
                onPressed: counter.increment,
                icon: Icon(Icons.add),
                label: Text('增加'),
                style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
              );
            },
          ),
        ],
      ),
    );
  }
}

// 另一个显示计数的组件 - 演示多个组件可以同时监听同一个数据
class AnotherCounterDisplay extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Consumer<CounterModel>(
      // 使用 child 参数优化性能 - 不变的部分不会重建
      child: Container(
        padding: EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: Colors.orange.shade100,
          borderRadius: BorderRadius.circular(5),
        ),
        child: Text(
          '我也在监听计数变化',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
      ),
      builder: (context, counter, child) {
        print("🔢 AnotherCounterDisplay 重建,计数值: ${counter.count}");
        
        return Container(
          margin: EdgeInsets.all(20),
          padding: EdgeInsets.all(15),
          decoration: BoxDecoration(
            color: Colors.orange.shade50,
            borderRadius: BorderRadius.circular(10),
            border: Border.all(color: Colors.orange),
          ),
          child: Column(
            children: [
              child!, // 这部分不会重建
              SizedBox(height: 10),
              Text(
                '计数的平方: ${counter.count * counter.count}',
                style: TextStyle(fontSize: 20, color: Colors.orange.shade800),
              ),
            ],
          ),
        );
      },
    );
  }
}

执行流程分析

让我们看看点击"增加"按钮时的完整执行流程:

1. 用户点击按钮

🎮 用户点击"增加"按钮
    ↓
📊 CounterModel.increment() 被调用
    ↓ 
📊 CounterModel: count 增加到 1,通知监听器
    ↓
📢 _onModelChanged() 被调用
    ↓
📢 数据模型变化,触发 setState

2. Provider 重建流程

🎨 ChangeNotifierProvider build
    ↓
⚖️ _InheritedProvider updateShouldNotify: true
    ↓
📢 通知所有依赖的子组件

3. 子组件更新流程

🔢 CounterDisplay didChangeDependencies (如果是首次)
    ↓
🔢 CounterDisplay 重建,新的计数值: 1
    ↓
🔢 AnotherCounterDisplay didChangeDependencies (如果是首次)
    ↓
🔢 AnotherCounterDisplay 重建,计数值: 1

注意StaticInfoWidgetCounterHomePage 不会重建!

性能优化技巧

1. 使用 child 参数避免不必要的重建

Consumer<CounterModel>(
  // 这部分不会因为数据变化而重建
  child: ExpensiveWidget(),
  
  builder: (context, counter, child) {
    return Column(
      children: [
        Text('计数: ${counter.count}'), // 会重建
        child!,                        // 不会重建
      ],
    );
  },
)

2. 选择性监听

class SmartWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // 只获取数据,不监听变化
    final counter = ChangeNotifierProvider.of<CounterModel>(context, listen: false);
    
    return ElevatedButton(
      onPressed: counter.increment, // 只是调用方法,不需要监听数据变化
      child: Text('增加'),
    );
  }
}

3. 多个 Provider 的组合使用

class MultiProviderApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider<CounterModel>(
        create: () => CounterModel(),
        child: ChangeNotifierProvider<UserModel>(
          create: () => UserModel(),
          child: ChangeNotifierProvider<ThemeModel>(
            create: () => ThemeModel(),
            child: MyHomePage(),
          ),
        ),
      ),
    );
  }
}

// 在子组件中使用多个 Provider
class MultiConsumerWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Consumer<CounterModel>(
      builder: (context, counter, _) {
        return Consumer<UserModel>(
          builder: (context, user, _) {
            return Text('${user.name}: ${counter.count}');
          },
        );
      },
    );
  }
}

与原生 Provider 包的对比

我们实现的简化版 Provider 具备了核心功能:

功能 我们的实现 Provider 包
基本状态管理
精确更新
类型安全
性能优化
多 Provider 支持
Selector 优化
ProxyProvider
更多便捷 API

总结

通过实现这个 Provider 模式,我们深入理解了:

  1. InheritedWidget 的实际应用:如何将理论知识转化为实用的状态管理方案
  2. 缓存机制的重要性:避免不必要的组件重建
  3. 观察者模式ChangeNotifier 如何通知数据变化
  4. 性能优化策略:选择性监听、子组件缓存等技巧

网站公告

今日签到

点亮在社区的每一天
去签到