AsyncRedux连接器模式Skill asyncredux-connector-pattern

AsyncRedux连接器模式是Flutter框架中用于分离智能和愚蠢小部件的设计模式,通过ViewModel和VmFactory实现状态管理和UI更新优化,提升代码可测试性、可维护性和可重用性。关键词:Flutter, AsyncRedux, 连接器模式, 状态管理, ViewModel, VmFactory, 移动开发

移动开发 0 次安装 0 次浏览 更新于 3/19/2026

名称: asyncredux-connector-pattern 描述: 实现连接器模式以分离智能和愚蠢小部件。覆盖创建StoreConnector小部件、实现VmFactory和Vm类、构建视图模型以及通过视图模型等式优化重建。

概述

连接器模式将存储访问逻辑与UI呈现分离。小部件不直接通过context.statecontext.dispatch()访问存储,而是通过“智能”连接器小部件提取存储数据,并通过构造函数参数传递给“愚蠢”表示小部件。

为什么使用连接器模式?

  1. 测试简化 - 无需创建Redux存储即可测试UI小部件,通过传递模拟数据
  2. 关注点分离 - UI小部件专注于外观;连接器处理业务逻辑
  3. 可重用性 - 表示小部件独立于Redux工作
  4. 代码清晰度 - 小部件代码不会因状态访问和转换逻辑而杂乱
  5. 优化重建 - 仅在视图模型更改时重建

三个组件

1. 视图模型 (Vm)

仅包含UI小部件所需的数据。扩展Vm并列出相等字段:

class CounterViewModel extends Vm {
  final int counter;
  final String description;
  final VoidCallback onIncrement;

  CounterViewModel({
    required this.counter,
    required this.description,
    required this.onIncrement,
  }) : super(equals: [counter, description]);
}

equals列表告诉AsyncRedux在决定是否重建时比较哪些字段。回调(如onIncrement)不应包含在equals中。

2. VmFactory

将存储状态转换为视图模型。扩展VmFactory并实现fromStore()

class CounterFactory extends VmFactory<AppState, CounterConnector, CounterViewModel> {
  CounterFactory(connector) : super(connector);

  @override
  CounterViewModel fromStore() => CounterViewModel(
    counter: state.counter,
    description: state.description,
    onIncrement: () => dispatch(IncrementAction()),
  );
}

工厂可以访问:

  • state - 工厂创建时的存储状态
  • dispatch() - 从回调中调度操作
  • dispatchSync() - 用于同步调度
  • connector - 父连接器小部件的引用

3. StoreConnector

桥接存储和UI小部件:

class CounterConnector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, CounterViewModel>(
      vm: () => CounterFactory(this),
      builder: (BuildContext context, CounterViewModel vm) => CounterWidget(
        counter: vm.counter,
        description: vm.description,
        onIncrement: vm.onIncrement,
      ),
    );
  }
}

“愚蠢”小部件通过构造函数参数接收数据:

class CounterWidget extends StatelessWidget {
  final int counter;
  final String description;
  final VoidCallback onIncrement;

  const CounterWidget({
    required this.counter,
    required this.description,
    required this.onIncrement,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$counter'),
        Text(description),
        ElevatedButton(
          onPressed: onIncrement,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

重建优化

每次操作更改存储状态时,StoreConnector比较新视图模型与先前视图模型。仅当它们不同时重建(基于equals列表)。

为了防止状态更改时重建,使用notify: false

dispatch(MyAction(), notify: false);

高级工厂技术

访问连接器属性

从连接器小部件传递数据到工厂:

class UserConnector extends StatelessWidget {
  final int userId;
  const UserConnector({required this.userId});

  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, UserViewModel>(
      vm: () => UserFactory(this),
      builder: (context, vm) => UserWidget(user: vm.user),
    );
  }
}

class UserFactory extends VmFactory<AppState, UserConnector, UserViewModel> {
  UserFactory(connector) : super(connector);

  @override
  UserViewModel fromStore() => UserViewModel(
    // 在此访问connector.userId
    user: state.users.firstWhere((u) => u.id == connector.userId),
  );
}

state vs currentState()

在工厂内部:

  • state - 工厂创建时的状态(最终,不会更改)
  • currentState() - 调用时的当前存储状态

这些通常匹配,但在dispatchSync()后的回调中可能不同:

@override
UserViewModel fromStore() => UserViewModel(
  onSave: () {
    dispatchSync(SaveAction());
    // state仍有旧值
    // currentState()有SaveAction后的新值
  },
);

在回调中使用vm Getter

在回调中访问已计算的视图模型字段以避免冗余计算:

@override
UserViewModel fromStore() => UserViewModel(
  name: state.user.name,
  onSave: () {
    // 使用vm.name而非从state重新计算
    print('Saving user: ${vm.name}');
    dispatch(SaveAction(vm.name));
  },
);

注意: vm getter仅在fromStore()完成后可用。在回调中使用,而不是在视图模型构造期间。

基础工厂模式

创建基础工厂以减少样板代码:

abstract class BaseFactory<T extends StatelessWidget, Model extends Vm>
    extends VmFactory<AppState, T, Model> {
  BaseFactory(T connector) : super(connector);

  // 常见getter
  User get user => state.user;
  Settings get settings => state.settings;
}

class MyFactory extends BaseFactory<MyConnector, MyViewModel> {
  MyFactory(connector) : super(connector);

  @override
  MyViewModel fromStore() => MyViewModel(
    user: user,  // 使用继承的getter
  );
}

可空视图模型

当无法生成有效视图模型时(例如,数据仍在加载),返回null:

class HomeConnector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, HomeViewModel?>(  // 可空类型
      vm: () => HomeFactory(this),
      builder: (BuildContext context, HomeViewModel? vm) {  // 可空参数
        return (vm == null)
          ? Text("用户未登录")
          : HomePage(user: vm.user);
      },
    );
  }
}

class HomeFactory extends VmFactory<AppState, HomeConnector, HomeViewModel?> {
  HomeFactory(connector) : super(connector);

  @override
  HomeViewModel? fromStore() {  // 可空返回
    return (state.user == null)
      ? null
      : HomeViewModel(user: state.user!);
  }
}

从flutter_redux迁移

如果从flutter_redux迁移,可以使用converter参数代替vm

class MyConnector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, ViewModel>(
      converter: (store) => ViewModel.fromStore(store),
      builder: (context, vm) => MyWidget(name: vm.name),
    );
  }
}

class ViewModel extends Vm {
  final String name;
  final VoidCallback onSave;

  ViewModel({required this.name, required this.onSave})
    : super(equals: [name]);

  static ViewModel fromStore(Store<AppState> store) {
    return ViewModel(
      name: store.state.name,
      onSave: () => store.dispatch(SaveAction()),
    );
  }
}

注意:vmconverter互斥。对于新代码,推荐vm方法。

调试重建

要观察连接器何时重建,将modelObserver传递给存储:

var store = Store<AppState>(
  initialState: AppState.initialState(),
  modelObserver: DefaultModelObserver(),
);

在StoreConnector中添加debug: this以在日志中显示连接器类型名称:

StoreConnector<AppState, ViewModel>(
  debug: this,
  vm: () => Factory(this),
  builder: (context, vm) => MyWidget(vm: vm),
);

覆盖toString()在视图模型中以自定义诊断输出:

class MyViewModel extends Vm {
  final int counter;
  MyViewModel({required this.counter}) : super(equals: [counter]);

  @override
  String toString() => 'MyViewModel{counter: $counter}';
}

控制台输出显示重建信息:

Model D:1 R:1 = Rebuild:true, Connector:MyWidgetConnector, Model:MyViewModel{counter: 5}

测试视图模型

使用Vm.createFrom()在隔离环境中测试视图模型:

test('view-model properties', () {
  var store = Store<AppState>(initialState: AppState(name: "Mary"));
  var vm = Vm.createFrom(store, MyFactory());

  expect(vm.name, "Mary");
});

test('view-model callbacks dispatch actions', () async {
  var store = Store<AppState>(initialState: AppState(name: "Mary"));
  var vm = Vm.createFrom(store, MyFactory());

  vm.onChangeName("Bill");
  await store.waitActionType(ChangeNameAction);
  expect(store.state.name, "Bill");
});

重要: Vm.createFrom()每个工厂实例只能调用一次。为每个测试创建新工厂。

完整示例

// 状态
class AppState {
  final int counter;
  final String description;
  AppState({required this.counter, required this.description});
  AppState copy({int? counter, String? description}) => AppState(
    counter: counter ?? this.counter,
    description: description ?? this.description,
  );
}

// 操作
class IncrementAction extends ReduxAction<AppState> {
  @override
  AppState reduce() => state.copy(counter: state.counter + 1);
}

// 视图模型
class CounterViewModel extends Vm {
  final int counter;
  final String description;
  final VoidCallback onIncrement;

  CounterViewModel({
    required this.counter,
    required this.description,
    required this.onIncrement,
  }) : super(equals: [counter, description]);
}

// 工厂
class CounterFactory extends VmFactory<AppState, CounterConnector, CounterViewModel> {
  CounterFactory(connector) : super(connector);

  @override
  CounterViewModel fromStore() => CounterViewModel(
    counter: state.counter,
    description: state.description,
    onIncrement: () => dispatch(IncrementAction()),
  );
}

// 连接器(智能小部件)
class CounterConnector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, CounterViewModel>(
      vm: () => CounterFactory(this),
      builder: (context, vm) => CounterWidget(
        counter: vm.counter,
        description: vm.description,
        onIncrement: vm.onIncrement,
      ),
    );
  }
}

// 表示小部件(愚蠢小部件)
class CounterWidget extends StatelessWidget {
  final int counter;
  final String description;
  final VoidCallback onIncrement;

  const CounterWidget({
    required this.counter,
    required this.description,
    required this.onIncrement,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('$counter', style: TextStyle(fontSize: 48)),
        Text(description),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: onIncrement,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

参考资料

文档中的URL: