名称: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(),
),
);
}
dependencies和configuration参数是接收Store的工厂,因此它们可以读取store.environment来改变其行为。
步骤 5:通过基操作类从操作访问
定义一个基操作类,带有dependencies、environment和configuration的类型化获取器:
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扩展。environment和configuration可通过getEnvironment和getConfiguration访问。注意: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持有功能标志 - 可测试性:通过更改环境交换实现,无需更改操作代码
- 类型安全:基操作类中的类型化获取器提供编译时检查
- 工厂模式:
dependencies和configuration工厂接收Store,允许它们基于environment变化 - 作用域依赖:每个存储实例都有自己的环境/依赖/配置,防止测试污染
参考
文档中的URL:
- https://asyncredux.com/sitemap.xml
- https://asyncredux.com/flutter/miscellaneous/dependency-injection
- https://asyncredux.com/flutter/testing/mocking
- https://asyncredux.com/flutter/basics/store
- https://asyncredux.com/flutter/advanced-actions/redux-action
- https://asyncredux.com/flutter/connector/store-connector
- https://asyncredux.com/flutter/testing/store-tester
- https://asyncredux.com/flutter/testing/dispatch-wait-and-expect
- https://github.com/marcglasberg/async_redux/blob/master/example/lib/main_dependency_injection.dart