Flutter 状态管理极速版:view_model

245 阅读3分钟

在 Flutter 开发领域,状态管理框架层出不穷,Provider、Riverpod、Bloc、Signal 和 Fish Redux 等都是其中的佼佼者。然而,这些框架在实际使用过程中,普遍存在操作复杂的问题,并且过度关注局部状态的更新。

  • Provider:该框架仅支持在 Widget 树的上下层级间共享状态。这就导致处于同级树结构,比如不同路由页面之间,无法直接共享状态。开发者往往只能将状态提升到 Application 层的 Widget 树中进行管理。
  • Bloc 和 Signal:这两个框架的状态共享机制基于 Provider 构建,因此同样存在上述共享范围受限的问题。
  • Riverpod:为解决状态共享问题,Riverpod 舍弃了 Provider 的实现,自行开发了单例管理器,实现了状态的全局创建与销毁管理。但在实际使用中,它仍需依赖一层 Provider 来创建状态,即便官方提供了注解方式简化操作,整体流程依然较为繁琐。

回归初心,一个 ui 的状态管理应该具备哪些功能?

  • 存储 view 的状态
  • 声明式 ui,必须要有数据的监听
  • 可以在 Widget 之间共享
  • 可以跟着 Widget 自动销毁

局部的状态更新真的重要吗?

我们都知道 flutter 3 层树结构, Wiget 就是配置而已。 我们只要恰当的重写 State 的 didUpdate(StatefulWiget old),利用 Widget 自身的 diff 机制就不会有性能开销。每次 setState 不过是多了几个或者几十个 Widget 对象的内存开销而已,并且很快会被 gc 回收掉,实际的性能损坏对于大部分机器来说忽略不计。

基于此,一个轻量版的 view_model 如下:

view_model

  • 简单且轻量级。
  • 无复杂机制,基于StreamControllersetState实现。
  • 自动释放资源,遵循Statedispose方法。
  • 可在任意有状态组件(StatefulWidget)间共享。

ViewModel仅绑定到有状态组件(StatefulWidget)的State上。我们不建议将状态绑定到无状态组件(StatelessWidget)上,因为无状态组件不应有状态。

核心概念

  • ViewModel:存储状态并在状态改变时发出通知。
  • ViewModelFactory:指导如何创建你的ViewModel
  • getViewModel:创建或获取已存在的ViewModel
  • listenViewModelStateChanged:监听Widget.State内的状态变化。

使用方法

添加依赖

view_model:
  git:
    url: https://github.com/lwj1994/flutter_view_model
    ref: 0.0.1

定义ViewModel

import "package:view_model/view_model.dart";

class MyViewModel extends ViewModel<String> {
  MyViewModel({
    required super.state,
  }) {
    debugPrint("创建 MyViewModel,状态: $state,哈希码: $hashCode");
  }

  void setNewState() {
    setState((s) {
      return "hi";
    });
  }

  @override
  void dispose() async {
    super.dispose();
    debugPrint("释放 MyViewModel,状态: $state,哈希码: $hashCode");
  }
}

class MyViewModelFactory with ViewModelFactory<MyViewModel> {
  final String arg;

  MyViewModelFactory({this.arg = ""});

  @override
  MyViewModel build() {
    return MyViewModel(state: arg);
  }
}

在组件中使用ViewModel

import "package:view_model/view_model.dart";

class _State extends State<Page> with ViewModelStateMixin<Page> {
  // 建议使用getter来获取ViewModel
  MyViewModel get viewModel =>
      getViewModel<MyViewModel>(factory: MyViewModelFactory(arg: "初始参数"));

  // 获取ViewModel的状态
  String get state => viewModel.state;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          viewModel.setNewState();
        },
        child: Icon(Icons.add),
      ),
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () {
            appRouter.maybePop();
          },
        ),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text("mainViewModel.state = ${_mainViewModel.state}"),
          Text(
            state,
            style: const TextStyle(color: Colors.red),
          ),
          FilledButton(
              onPressed: () async {
                refreshViewModel(_mainViewModel);
              },
              child: const Text("刷新mainViewModel")),
          FilledButton(
              onPressed: () {
                debugPrint("页面的MyViewModel哈希码 = ${viewModel.hashCode}");
                debugPrint("页面的MyViewModel状态 = ${viewModel.state}");
              },
              child: const Text("打印MyViewModel")),
        ],
      ),
    );
  }
}

共享ViewModel

你可以设置unique() => true来在任意有状态组件(StateWidget)间共享同一个ViewModel实例。

import "package:view_model/view_model.dart";

class MyViewModelFactory with ViewModelFactory<MyViewModel> {
  final String arg;

  MyViewModelFactory({this.arg = ""});

  @override
  MyViewModel build() {
    return MyViewModel(state: arg);
  }

  // 如果为true,则会共享同一个viewModel实例。
  @override
  bool unique() => false;
}

监听状态变化

@override
void initState() {
  super.initState();
  listenViewModelStateChanged<MainViewModel, String>(
    _mainViewModel,
    onChange: (String? p, String n) {
      print("mainViewModel状态变化: $p -> $n");
    },
  );
}

刷新ViewModel

这将释放旧的ViewModel并创建一个新的。不过,建议使用getter来获取ViewModel,否则你需要手动重置ViewModel

// 建议使用getter来获取ViewModel。
MyViewModel get viewModel => getViewModel<MyViewModel>();

void refresh() {
  // 刷新 
  refreshViewModel(viewModel);
}

或者

late MyViewModel viewModel = getViewModel<MyViewModel>(factory: factory);

void refresh() {
  // 刷新并重置 
  refreshViewModel(viewModel);
  viewModel = getViewModel<MyViewModel>(factory: factory);
}

最后贴上 github: github.com/lwj1994/flu…