异步Redux依赖注入技能Skill asyncredux-dependency-injection

这个技能是关于在Flutter应用中使用AsyncRedux框架实现依赖注入,通过环境、依赖和配置模式来管理应用的服务和设置,以提高代码的可测试性和模块化。关键词包括:AsyncRedux、依赖注入、Flutter、Dart、环境模式、可测试性、状态管理。

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

名称:asyncredux-依赖注入 描述:使用环境、依赖和配置模式将依赖注入到操作中。涵盖创建环境枚举、依赖类、将它们传递给存储、从操作和小部件访问它们,以及使用依赖注入进行可测试性。

使用环境、依赖和配置进行依赖注入

AsyncRedux通过三个存储参数提供依赖注入:

  • environment:指定应用是否在生产、暂存、开发、测试等环境中运行。应该是不变的,在应用执行期间不改变。可以从操作和小部件访问。
  • dependencies:注入服务(如存储库、API等)的容器,通过接收Store的工厂创建,因此可以根据环境变化。通常不能从小部件访问。
  • configuration:用于功能标志和其他配置值。可以从操作和小部件访问。

步骤 1:定义环境

创建一个枚举(或类)来指定应用的运行上下文:

enum Environment {
  production,
  staging,
  testing;

  bool get isProduction => this == Environment.production;
  bool get isStaging => this == Environment.staging;
  bool get isTesting => this == Environment.testing;
}

步骤 2:定义依赖

创建一个抽象类,其工厂根据环境返回不同的实现:

abstract class Dependencies {
  factory Dependencies(Store store) {
    if (store.environment == Environment.production) {
      return DependenciesProduction();
    } else if (store.environment == Environment.staging) {
      return DependenciesStaging();
    } else {
      return DependenciesTesting();
    }
  }

  ApiClient get apiClient;
  AuthService get authService;
  int limit(int value);
}

class DependenciesProduction implements Dependencies {
  @override
  ApiClient get apiClient => RealApiClient();

  @override
  AuthService get authService => FirebaseAuthService();

  @override
  int limit(int value) => min(value, 5);
}

class DependenciesTesting implements Dependencies {
  @override
  ApiClient get apiClient => MockApiClient();

  @override
  AuthService get authService => MockAuthService();

  @override
  int limit(int value) => min(value, 1000); // 在测试中限制更高
}

步骤 3:定义配置(可选)

class Config {
  bool isABtestingOn = false;
  bool showAdminConsole = false;
}

步骤 4:将所有三个传递给存储

在创建存储时,传递环境、依赖工厂和配置工厂:

void main() {
  var store = Store<AppState>(
    initialState: AppState.initialState(),
    environment: Environment.production,
    dependencies: (store) => Dependencies(store),
    configuration: (store) => Config(),
  );

  runApp(
    StoreProvider<AppState>(
      store: store,
      child: MyApp(),
    ),
  );
}

dependenciesconfiguration参数是接收Store的工厂,因此它们可以读取store.environment来改变其行为。

步骤 5:通过基操作类从操作访问

定义一个基操作类,带有dependenciesenvironmentconfiguration的类型化获取器:

abstract class Action extends ReduxAction<AppState> {
  Dependencies get dependencies => super.store.dependencies as Dependencies;
  Environment get environment => super.store.environment as Environment;
  Config get config => super.store.configuration as Config;
}

现在在您的操作中使用它们:

class FetchUserAction extends Action {
  final String userId;
  FetchUserAction(this.userId);

  @override
  Future<AppState?> reduce() async {
    final user = await dependencies.apiClient.fetchUser(userId);
    return state.copy(user: user);
  }
}

class IncrementAction extends Action {
  final int amount;
  IncrementAction({required this.amount});

  @override
  AppState reduce() {
    int newState = state.counter + amount;
    int limitedState = dependencies.limit(newState);
    return state.copy(counter: limitedState);
  }
}

步骤 6:通过BuildContext扩展从小部件访问

创建一个BuildContext扩展。environmentconfiguration可通过getEnvironmentgetConfiguration访问。注意:dependencies通常不应从小部件访问。

extension BuildContextExtension on BuildContext {
  AppState get state => getState<AppState>();

  R select<R>(R Function(AppState state) selector) =>
      getSelect<AppState, R>(selector);

  /// 从小部件访问环境(不会触发重建)。
  Environment get environment => getEnvironment<AppState>() as Environment;

  /// 从小部件访问配置(不会触发重建)。
  Config get config => getConfiguration<AppState>() as Config;
}

在小部件中使用:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final env = context.environment;
    int counter = context.state;

    return Scaffold(
      appBar: AppBar(title: const Text('依赖注入示例')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 使用环境来改变UI。
            Text('运行在 ${env}。', textAlign: TextAlign.center),
            Text('$counter', style: const TextStyle(fontSize: 30)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => dispatch(IncrementAction(amount: 1)),
        child: const Icon(Icons.add),
      ),
    );
  }
}

步骤 7(如果使用StoreConnector):从VmFactory访问

如果使用StoreConnector,用类型化获取器扩展VmFactory

abstract class AppFactory<T extends Widget?, Model extends Vm>
    extends VmFactory<AppState, T, Model> {
  AppFactory([T? connector]) : super(connector);

  Dependencies get dependencies => store.dependencies as Dependencies;
  Environment get environment => store.environment as Environment;
  Config get config => store.configuration as Config;
}

使用不同环境进行测试

该模式通过注入测试实现使测试变得简单:

void main() {
  group('IncrementAction', () {
    test('使用测试依赖递增计数器', () async {
      var store = Store<AppState>(
        initialState: AppState(counter: 0),
        environment: Environment.testing,
        dependencies: (store) => Dependencies(store), // 返回DependenciesTesting
      );

      await store.dispatchAndWait(IncrementAction(amount: 5));

      // DependenciesTesting的限制为1000,所以值为5
      expect(store.state.counter, 5);
    });

    test('生产依赖限制计数器', () async {
      var store = Store<AppState>(
        initialState: AppState(counter: 3),
        environment: Environment.production,
        dependencies: (store) => Dependencies(store), // 返回DependenciesProduction
      );

      await store.dispatchAndWait(IncrementAction(amount: 10));

      // DependenciesProduction限制为5
      expect(store.state.counter, 5);
    });
  });
}

完整工作示例

import 'dart:math';
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';

late Store<int> store;

void main() {
  store = Store<int>(
    initialState: 0,
    environment: Environment.production,
    dependencies: (store) => Dependencies(store),
  );
  runApp(MyApp());
}

enum Environment {
  production,
  staging,
  testing;

  bool get isProduction => this == Environment.production;
  bool get isStaging => this == Environment.staging;
  bool get isTesting => this == Environment.testing;
}

abstract class Dependencies {
  factory Dependencies(Store store) {
    if (store.environment == Environment.production) {
      return DependenciesProduction();
    } else if (store.environment == Environment.staging) {
      return DependenciesStaging();
    } else {
      return DependenciesTesting();
    }
  }

  int limit(int value);
}

class DependenciesProduction implements Dependencies {
  @override
  int limit(int value) => min(value, 5);
}

class DependenciesStaging implements Dependencies {
  @override
  int limit(int value) => min(value, 25);
}

class DependenciesTesting implements Dependencies {
  @override
  int limit(int value) => min(value, 1000);
}

abstract class Action extends ReduxAction<int> {
  Dependencies get dependencies => super.store.dependencies as Dependencies;
}

class IncrementAction extends Action {
  final int amount;
  IncrementAction({required this.amount});

  @override
  int reduce() {
    int newState = state + amount;
    int limitedState = dependencies.limit(newState);
    return limitedState;
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreProvider<int>(
      store: store,
      child: MaterialApp(home: MyHomePage()),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final env = context.environment;
    int counter = context.state;

    return Scaffold(
      appBar: AppBar(title: const Text('依赖注入示例')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('运行在 ${env}。', textAlign: TextAlign.center),
            const Text(
              '您已按下按钮这么多次:
'
              '(受环境限制)',
              textAlign: TextAlign.center,
            ),
            Text('$counter', style: const TextStyle(fontSize: 30)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => dispatch(IncrementAction(amount: 1)),
        child: const Icon(Icons.add),
      ),
    );
  }
}

extension BuildContextExtension on BuildContext {
  int get state => getState<int>();
  int read() => getRead<int>();
  R select<R>(R Function(int state) selector) => getSelect<int, R>(selector);
  R? event<R>(Evt<R> Function(int state) selector) => getEvent<int, R>(selector);
  Environment get environment => getEnvironment<int>() as Environment;
}

关键优势

  • 关注点分离environment标识运行上下文,dependencies提供服务,configuration持有功能标志
  • 可测试性:通过更改环境交换实现,无需更改操作代码
  • 类型安全:基操作类中的类型化获取器提供编译时检查
  • 工厂模式dependenciesconfiguration工厂接收Store,允许它们基于environment变化
  • 作用域依赖:每个存储实例都有自己的环境/依赖/配置,防止测试污染

参考

文档中的URL: