likes
comments
collection
share

【Flutter】状态管理插件的”四大天王“简单原理与使用方式对比

作者站长头像
站长
· 阅读数 27

主流的状态管理插件的原理与使用对比

前言

目前的情况是,入门是从Getx开始的,后来有项目就转投了Bloc,目前如果有新项目的话想尝试一下 riverpod + hook 的方案。

目前除了 Flutter 主流的状态管理四大天王,Provider,Riverpod,GetX,Bloc之外,还有 MobX ,Redux ,Binders ,RxState ,GreenCat ,Seperated View ,StateX ,Rams,Signal 等等框架真的数不胜数。另外,Flutter官方也在积极研究统一的状态管理解决方案,未来可能会推出官方推荐的最佳实践。

学不完,真的学不完。

【Flutter】状态管理插件的”四大天王“简单原理与使用方式对比

本文就只浅析一下四大天王的基本使用与基本原理与他们的差异。

一、Provider

Provider 它允许在Flutter应用中高效地传递数据和状态。使用Provider时,通常不会修改原有数据类型来使其响应式。相反,Provider通过在Widget树中向下传递数据和对象,使得依赖于这些数据的Widget可以重建来响应状态变化。

当数据变化时,你通常会调用notifyListeners()(在ChangeNotifier的实现中),这会通知所有监听器(即依赖该数据的Widgets),触发它们的重建。因此,Provider不直接修改响应对象,而是依赖于ChangeNotifier或其他机制来通知状态变化。

Provider实际上是建立在Flutter的InheritedWidget机制之上的。InheritedWidget是一种有效的数据传递机制,允许数据在Widget树中从上向下传递。这意味着位于树中较低位置的Widgets可以访问在树中较高位置定义的数据。

Provider简化了InheritedWidget的使用,使状态管理和数据传递变得更加直观和简单。通过使用Provider,开发者可以更容易地访问和操作跨组件的共享数据。

一句话总结 : Provider是一种依赖注入框架,其状态管理功能基于InheritedWidgetListenable实现。特别是ChangeNotifierProvider是基于InheritedWidget构建的,并且其内部通知机制依赖于ChangeNotifier及其notifyListeners()方法来实现。

代码示例:

class Counter with ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();
  }
}

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);  //需要buildContext的环境
    
    return Text('Count: ${counter.count}');
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(  //这里通过ChangeNotifierProvider指定 ChangeNotifier
      create: (_) => Counter(),
      child: CounterWidget(),
    );
  }
}

二、Riverpod

Riverpod 是Provider的替代品,由同一作者开发。它解决了Provider的一些限制,如全局访问、改进的类型安全和测试性。与Provider不同,Riverpod不依赖于Widget树,因此可以更灵活地使用。

Riverpod被设计为高效且可预测的状态管理工具。由于它不依赖于BuildContext,可以避免Provider中的一些性能问题和限制。

Riverpod同样不要求修改数据类型来使其响应式。它通过不同类型的Providers(如StateProviderStateNotifierProvider等)来管理状态。

这些Providers管理着状态的读取和更新,当状态变化时,依赖于这些状态的Widgets会被自动重建。Riverpod也提供了StateNotifier,它类似于Provider中的ChangeNotifier,但是设计得更为灵活和强大。通过StateNotifier,你可以管理更复杂的状态逻辑,同时保持UI的响应性。

代码示例 StateProvider:

final counter1Provider = StateProvider((ref) => 0);  //创建与管理基本数据

class Home extends ConsumerWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) {
            final counter = ref.watch(counter1Provider);
            return Text('$counter');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          ref.read(counter1Provider.notifier).state++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

代码示例 ChangeNotifierProvider:

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

final counter2Provider = ChangeNotifierProvider((ref) => Counter());  //创建与管理对象

class Home extends ConsumerWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) {
            final counter = ref.watch(counter2Provider);
            return Text('${counter.count}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          ref.read(counter2Provider.notifier).increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

三、Provider VS Riverpod

Providerriverpod确实在概念上有一些相似之处,因为它们都用于状态管理和依赖注入,但它们的实现和使用方式有所不同,这里解释这两种框架的不同之处以及InheritedWidget在它们中的作用。

Provider是一个流行的Flutter依赖注入库,主要是基于InheritedWidget实现的。ChangeNotifierProvider是它提供的一种Provider,用于将ChangeNotifier的实例提供给其子Widget,并在状态变化时通知它们。当你使用ChangeNotifierProvider时,你会在Widget树中的某个节点处提供一个ChangeNotifier实例,并且只有当notifyListeners被调用时,该Provider的依赖者(子Widget)才会重建。

ChangeNotifierProvider(
  create: (context) => SomeModel(),
  child: SomeWidget(),
)

这里ChangeNotifierProvider使用了InheritedWidget来传递数据,当使用context.watch()Consumer等API时,它们实际上在内部查找最近的InheritedProvider<SomeModel>来获取模型实例。

riverpod则完全不同,它不依赖于InheritedWidgetriverpod是一个独立于Flutter框架之外的、可以与Flutter一起使用的状态管理库。在使用riverpod时,你不会直接创建一个InheritedWidget。相反,所有的providers都定义在全局范围内,你会使用ProviderScope来确保providers可以在Widget树中任何地方使用。

riverpod中,ProviderScope并不是一个InheritedWidget,它更像是一个状态容器,允许你在应用的任何地方访问providers。ProviderScope创建了一个状态存储,你可以在此之下的Widget树中任何地方通过context.readcontext.watchConsumer等API访问providers。

所以尽管他们都是基于Listenable来通知的 ,但是他们的作用域和传递数据的方式是不同的。

Riverpod在使用时,需要将ProviderScope放置在main()函数中并作为runApp的参数,确保了从MyApp开始的整个Widget树都能够访问到通过riverpod定义的providers。无论是状态管理还是依赖注入,这种做法都提供了一个全局的作用域,允许在应用的任何位置方便地使用这些providers。

如果不想改变 StatelessWidget、StatefulWidget 这样的布局结构,不想使用自定义的 ConsumerWidget 我们也能这么使用:

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}


final counter2Provider = ChangeNotifierProvider((ref) => Counter()); //创建与管理对象

class Home extends StatelessWidget {
  Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) {
            final counter = ref.watch(counter2Provider);
            return Text('${counter.count}');
          },
        ),
      ),
      floatingActionButton: Consumer(
        builder: (context, ref, _) {
          return FloatingActionButton(
            onPressed: () {
              ref.read(counter2Provider.notifier).increment();
            },
            child: const Icon(Icons.add),
          );
        },
      ),
    );
  }
  
}

四、Getx

Getx 也是我们熟悉的选手,GetX是Flutter中一个很流行的库,提供了状态管理、路由管理、依赖注入等多种功能。以下是使用GetX进行状态管理的一些基础步骤

一般我们使用控制器来承载响应式变量:

import 'package:get/get.dart';

class UserController extends GetxController {
  var userName = ''.obs;
  var userAge = 0.obs;

  void updateUser(String name, int age) {
    userName.value = name;
    userAge.value = age;
  }
}

注册控制器:

final userController = Get.put(UserController());

在布局中使用状态:

Obx(() => Text("User Name: ${userController.userName}"));

当你需要更新状态时,直接在控制器中修改状态变量。

userController.updateUser('New Name', 25);

一旦调用了这个方法,所有使用该状态的 Obx() 内部部件会自动更新UI。

除了 obs 的方案,我们还能使用 GetBuilder 来管理指定范围的更新。只是这是手动的刷新,需要手动的调用update[tag] 来刷新指定的 GetBuilder 内部的布局。

其实 Getx 和 Provider 类似,需要 ChangeNotifier 或 GetxController 来承载,在布局中需要使用 Consumer 或者 Obx 来包裹布局。并且他们带有状态管理和依赖注入的功能。只是 Getx 改变了对象的属性,从 String 改为了 RxString。

并且 Getx 和 Provider/Riverpod 的实现原理也不同,GetX状态管理不是基于Flutter核心框架的InheritedWidget或Listenable来实现的。相反,它采用了自己的方式来实现状态管理和响应式编程,主要基于Rx(Reactive Extensions)实现的。

使用 .obs 标记的变量,实际上是一个Rx类型的对象,监听这些变量的Widget可以自动重新构建。

总之,GetX通过Rx和自己的依赖管理机制提供了一个强大且高效的状态管理解决方案。这与Flutter的InheritedWidget和Listenable等内建机制有本质的不同,使得开发者能够以更解耦和响应式的方式来管理应用状态。这种方式不仅提高了开发效率,还有助于提升应用的性能和可维护性。

五、Bloc

Bloc 是一种在Flutter中用于状态管理的架构模式。它的设计目的是将业务逻辑与界面展示分离,使得状态管理更加清晰和可预测。BLoC 使用了响应式编程(特别是通过使用 Streams),以实现数据流的管理和组件间的通信。

flutter_bloc 中,BlocCubit 是用于状态管理的两个主要概念,它们都用于将业务逻辑从界面层分离出来,以帮助实现更可预测的状态管理和更易于测试的代码。

Bloc 相对于 Cubit 更复杂,它基于事件的概念。你需要定义事件和状态,然后实现事件到状态的映射,从 UI 层发出事件,Bloc 接受这些事件,并根据当前状态和接收到的事件通过 mapEventToState 函数来决定下一个状态。

Cubit 是 Bloc 的一个简化版本,它不依赖于事件的概念,而是直接对函数调用做出响应。在 Cubit 中,你直接调用一个函数来改变状态。这个函数内部会执行一些逻辑,并最终调用 emit 来发出新的状态。

与 Bloc 相比,Cubit 不需要定义事件,也不需要通过事件来触发状态变化,因此在实现上更为简洁。由于其简洁性,特别适用于那些不需要复杂事件处理的场景,可快速实施状态管理。Cubit 可以看做是 Bloc 的青春版,一个场景化的用法。

本次的示例就以 Cubit 状态为模板。后期单独讲 Bloc 的时候再两者单独说。

class CounterState extends Equatable {
  final int counterValue;
  final bool wasIncremented;

  CounterState({required this.counterValue, required this.wasIncremented});

  @override
  List<Object> get props => [counterValue, wasIncremented];
}

class Counter2Cubit extends Cubit<CounterState> {
  Counter2Cubit() : super(CounterState(counterValue: 0, wasIncremented: true));

  void increment() => emit(CounterState(counterValue: state.counterValue + 1, wasIncremented: true));
  void decrement() => emit(CounterState(counterValue: state.counterValue - 1, wasIncremented: false));
}

...
     BlocProvider(
        create: (_) => Counter2Cubit(),
        child: BlocCounterPage(),
      ) 
...

// Bloc对应的页面
class BlocCounterPage extends StatelessWidget {
  const BlocCounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: BlocBuilder<Counter2Cubit, CounterState>(
        builder: (context, state) => Center(
          child: Text(
            '${state.counterValue}, was incremented: ${state.wasIncremented}',
          ),
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () => context.read<Counter2Cubit>().increment(),
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.remove),
            onPressed: () => context.read<Counter2Cubit>().decrement(),
          ),
        ],
      ),
    );
  }
}

常用 API :

提供者

BlocProvider它提供了一种方式来将Bloc或Cubit传递给一个小部件树。任何子小部件都可以通过context来获取这个Bloc或Cubit。

BlocProvider(
  create: (context) => CounterCubit(),
  child: CounterPage(),
),

MultiBlocProvider 是BlocProvider的一个方便的封装,允许您一次性向子树提供多个Bloc或Cubit。

MultiBlocProvider(
  providers: [
    BlocProvider<CounterCubit>(
      create: (BuildContext context) => CounterCubit(),
    ),
    BlocProvider<ThemeCubit>(
      create: (BuildContext context) => ThemeCubit(),
    ),
  ],
  child: MyApp(),
),

RepositoryProvider 类似于 BlocProvider,但它不是用于提供 Bloc 或 Cubit,而是用于提供数据仓库或任何类型的数据服务。通过使用 RepositoryProvider,您可以在需要的地方注入数据源,以便它们可以在 widget 树中的任何地方被访问。

RepositoryProvider(
  create: (context) => UserRepository(),
  child: MyApp(),
),

MultiRepositoryProvider 允许您在一个地方注入多个数据仓库或服务。这在您的应用需要多个数据源时非常有用,可以帮助您保持代码的清晰和组织。

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<UserRepository>(
      create: (context) => UserRepository(),
    ),
    RepositoryProvider<ProductRepository>(
      create: (context) => ProductRepository(),
    ),
  ],
  child: MyApp(),
),

在页面中接收Reposotory:

context.read<RepositoryA>();  //扩展的方式

RepositoryProvider.of<RepositoryA>(context)  //非扩展的方式

接收者:

BlocBuilder是一个Flutter小部件,它要求提供一个Bloc或Cubit和一个builder函数。当给定的Bloc或Cubit的状态发生变化时BlocBuilder将自动重建。注意它是返回Widget的。

BlocBuilder<CounterCubit, int>(
  builder: (context, count) => Text('$count'),
),

如果想接收多个Provider的Bloc或Cubit状态变化,可以分开接收:

Column(
  children: [
    BlocBuilder<CounterCubit, CounterState>(
      builder: (context, countState) {
        // 使用 countState 构建 UI
      },
    ),
    BlocBuilder<ThemeCubit, ThemeState>(
      builder: (context, themeState) {
        // 使用 themeState 构建 UI
      },
    ),
  ],
)

也可以嵌套接收:

BlocBuilder<CounterCubit, CounterState>(
  builder: (context, countState) {
    return BlocBuilder<ThemeCubit, ThemeState>(
      builder: (context, themeState) {
        // 结合 countState 和 themeState 构建 UI
      },
    );
  },
)

BlocListener是一个Flutter小部件,用于监听Bloc或Cubit的状态变化,并可以触发基于状态变化的动作。注意他不返回Widget。

BlocListener<CounterCubit, int>(
  listener: (context, count) {
    if (count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Count reached 10')),
      );
    }
  },
  child: Container(),
),

MultiBlocListener是BlocListener的一个方便的封装,允许您一次性向小部件树提供多个BlocListener。

MultiBlocListener(
  listeners: [
    BlocListener<CounterCubit, int>(
      listener: (context, state) {},
    ),
    BlocListener<ThemeCubit, ThemeData>(
      listener: (context, themeData) {},
    ),
  ],
  child: MyApp(),
),

BlocConsumer是BlocBuilder和BlocListener的组合,它可以同时监听状态变化并重建UI。

BlocConsumer<CounterCubit, int>(
  listener: (context, count) {
    if (count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Count reached 10')),
      );
    }
  },
  builder: (context, count) => Center(child: Text('$count')),
),

BlocSelector它允许您根据 Bloc 或 Cubit 的状态选择性地重建 UI 部分。与 BlocBuilder 类似,BlocSelector 用于侦听 Bloc 或 Cubit 状态的变化,但它提供了额外的选择功能,允许您指定一个选择器函数来决定何时重建 UI。

BlocSelector<UserBloc, UserState, String>(
  selector: (state) => state.username,
  builder: (context, username) {
    return Text(username);
  },
);

总的来说,熟悉 Android 开发的很容易上手 Bloc ,连命名的规范都很类似,开发模式也很类似。

bloc 的实现原理与 Provider和Riderpod 又不同,不是基于 InheritedWidget、Listenable 实现的,它利用了 Dart 的 Stream 和 StreamController 来处理不同状态的流和事件的分发。

六、原生实现

我们知道有些框架是基于原生的 nheritedWidget、Listenable 来实现状态管理,有些框架是基于 Stream 来实现状态管理,其实我们不使用框架直接手撕也是能自行实现的。

// 1. 定义状态

abstract class CounterState {}
class CounterInitial extends CounterState {}
class CounterValue extends CounterState {
  final int value;
  CounterValue(this.value);
}

// 2. 定义事件

abstract class CounterEvent {}
class CounterIncrement extends CounterEvent {}

// 3. 创建 BLoC 类

class CounterBloc {
  int _counter = 0;

  final _stateController = StreamController<CounterState>();
  StreamSink<CounterState> get _inCounter => _stateController.sink;
  Stream<CounterState> get counter => _stateController.stream;

  final _eventController = StreamController<CounterEvent>();
  Sink<CounterEvent> get counterEventSink => _eventController.sink;

  CounterBloc() {
    _eventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CounterEvent event) {
    if (event is CounterIncrement) {
      _counter++;
      _inCounter.add(CounterValue(_counter));
    }
  }

  void dispose() {
    _stateController.close();
    _eventController.close();
  }
}

// 4. 在 UI 中使用

void main() {
  final counterBloc = CounterBloc();

  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Text('BLoC Example')),
      body: StreamBuilder(
        stream: counterBloc.counter,
        initialData: CounterInitial(),
        builder: (BuildContext context, AsyncSnapshot<CounterState> snapshot) {
          if (snapshot.data is CounterValue) {
            return Center(
              child: Text(
                'You hit me: ${(snapshot.data as CounterValue).value} times',
              ),
            );
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counterBloc.counterEventSink.add(CounterIncrement()),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    ),
  ));
}

其实同样的我们基于 ChangeNotifier + Listenable 一样能实现类似的功能:

class CounterModel with ChangeNotifier {
  final ValueNotifier<int> _count = ValueNotifier(0);

  ValueNotifier<int> get count => _count;

  void increment() {
    _count.value++;
    notifyListeners();
  }
}

使用:

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {

  final CounterModel _counterModel = CounterModel();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Listenable Example')),
        body: ValueListenableBuilder(
          valueListenable: _counterModel.count,
          builder: (context, value, child) {
            return Text('You have pushed the button $value times');
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _counterModel.increment();
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

只是没有依赖注入的效果或Provider的效果,对于对象的监听我们也可以做到:

class CounterModel with ChangeNotifier {
  final ValueNotifier<UserProfile> _userProfile = ValueNotifier(UserProfile(0, "test name"));
  final ValueNotifier<int> _count = ValueNotifier(0);

  ValueNotifier<UserProfile> get userProfile => _userProfile;
  ValueNotifier<int> get count => _count;

  void increment() {
    //如果是对象,需要每次都拷贝对象,更换对象内存地址
    _userProfile.value = UserProfile(_userProfile.value.id + 1, _userProfile.value.name);
    _count.value++;
    notifyListeners();
  }

}

使用的时候:

     ValueListenableBuilder(
          valueListenable: _counterModel.userProfile,
          builder: (context, value, child) {
            return Text('You have pushed the button ${value.id} times');
          },
        )

如果一个 ValueListenableBuilder 想要监听多个 valueListenable 对象,我们也能实现,只是我们需要自定义一个组合器

class CompositeValueListenable extends ChangeNotifier implements ValueListenable<List<dynamic>> {
  final List<ValueListenable> _listenables;
  late List<dynamic> _values;

  CompositeValueListenable(this._listenables) {
    // 初始化_values为监听的ValueListenable的当前值
    _values = _listenables.map((listenable) => listenable.value).toList();

    // 为每个ValueListenable添加监听器
    for (var listenable in _listenables) {
      listenable.addListener(_listener);
    }
  }

  void _listener() {
    // 当任何一个ValueListenable变化时,更新_values并通知监听者
    _values = _listenables.map((listenable) => listenable.value).toList();
    notifyListeners();
  }

  @override
  List<dynamic> get value => _values;

  @override
  void dispose() {
    // 移除所有监听器
    for (var listenable in _listenables) {
      listenable.removeListener(_listener);
    }
    super.dispose();
  }
}

使用的时候:

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {

  final CounterModel _counterModel = CounterModel();
  
  @override
  Widget build(BuildContext context) {

    final compositeListenable = CompositeValueListenable([
      _counterModel.userProfile,
      _counterModel.count,
    ]);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Listenable Example')),
        body: ValueListenableBuilder(
          valueListenable: compositeListenable,
          builder: (context, values, child) {
            return Text('当前的用户id为:${values[0].id} 他的完成数据为:${values[1]}');
       
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _counterModel.increment();
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

效果:

【Flutter】状态管理插件的”四大天王“简单原理与使用方式对比

后记

当然除了以上这些状态管理的方案,还有后来者如 Signal ,也在崛起中。而 Signal 的实现思路又与上面四种方案有所差别。每个状态管理框架都试图以其独特的方式解决Flutter应用中状态管理的复杂性。搞得用户真的不知道怎么选择。

由于 Signal 的使用者还是少数不是主流,而以上主流的方案中根据难易程度,我觉得 GetX 是最简单入门的,其次是 Bloc 方案也很友好,再其次就是 RiverPod ,不过现在 RiverPod 都是结合 Hook 框架一起使用 hooks_riverpod: ^2.4.0 结合 flutter_hooks: ^0.20.1 最近也很流行。

我觉得这三种方案都是不错的选择,我之前的项目用到的是 Getx 现在的项目用的 Bloc 的方案,后期有新项目也会尝试 riverpod + hooks 的方案。

三种方案虽然内部实现的原理不同,但是使用下来感觉性能差异并不大,还是看开发者如何使用了,如果有这方面的性能对比,请各位大佬不吝赐教。

至于具体要选择哪一种方案真的还需要你综合使用门槛,社区活跃度等条件,稳定与熟悉也是比较重要的,最终还是需要你自己权衡,状态管理框架与页面和逻辑都关联比较深,是属于基建,一旦选择了后期很难更换。

如果你有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。

后期有时间我会单独的把几种框架单独拿出来详细说说,从一个 Android 开发者的角度如何快速入门这些状态管理插件。

由于不是具体的 Demo ,本文代码都已在文中贴出作为参考,如果感觉本文对你有一点的启发和帮助,还望你能点赞支持一下,你的支持对我真的很重要。

Ok,这一期就此完结。

【Flutter】状态管理插件的”四大天王“简单原理与使用方式对比

转载自:https://juejin.cn/post/7371720794979188777
评论
请登录