flutter-di-get-it
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter DI — get_it
Flutter DI — get_it
원칙
原则
- DI는 단일 엔트리 의
lib/core/di/di_setup.dart함수 하나에서 등록한다. Feature별로 분산하지 않는다 (단일 패키지 구조이므로).diSetup() - 에서
main()호출 전에runApp()을 반드시 호출한다.diSetup() - Root 위젯에서만 로 ViewModel을 꺼낸다. Screen 위젯이나 공용 위젯에는 절대
getIt<T>()을 쓰지 않는다 (테스트 용이성 확보).getIt - ViewModel은 항상 로 등록한다. 싱글톤이면 여러 화면이 같은 상태를 공유하게 되어 버린다.
registerFactory
- DI需在单一入口文件的
lib/core/di/di_setup.dart函数中统一注册,不按功能模块(Feature)分散注册(因采用单一包结构)。diSetup() - 必须在中调用
main()之前执行runApp()。diSetup() - 仅在Root组件中通过获取ViewModel。绝对不能在Screen组件或公共组件中使用
getIt<T>()(确保测试便利性)。getIt - ViewModel**必须始终通过**注册。若使用单例,多个页面会共享同一状态。
registerFactory
기본 진입점
基本入口
dart
// lib/core/di/di_setup.dart
final getIt = GetIt.instance;
void diSetup() {
// 1) data source
// 2) repository
// 3) use case
// 4) view model
}dart
// lib/main.dart
void main() {
diSetup();
runApp(const MyApp());
}등록 순서는 의존성 그래프의 리프부터 위로 올린다. DataSource → Repository → UseCase → ViewModel 순서여야 이 참조할 때 이미 등록되어 있다.
getIt()dart
// lib/core/di/di_setup.dart
final getIt = GetIt.instance;
void diSetup() {
// 1) data source
// 2) repository
// 3) use case
// 4) view model
}dart
// lib/main.dart
void main() {
diSetup();
runApp(const MyApp());
}注册顺序需从依赖图的叶子节点开始向上进行。必须遵循DataSource → Repository → UseCase → ViewModel的顺序,这样引用时才能确保对象已完成注册。
getIt()레이어별 등록 패턴
分层注册模式
DataSource (싱글톤)
DataSource(单例)
dart
getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());인터페이스 타입으로 등록해 구현체를 Root/ViewModel에서 절대 직접 참조하지 않게 만든다.
dart
getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());以接口类型注册,确保Root/ViewModel不会直接引用实现类。
Repository (싱글톤)
Repository(单例)
생성자 파라미터는 으로 풀어서 전달한다. 제네릭 추론이 필요한 경우 명시적으로 로 쓴다.
getIt()getIt<Foo>()dart
getIt.registerSingleton<RecipeRepository>(
MockRecipeRepositoryImpl(
recipeDataSource: getIt(),
),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());构造函数参数通过解析传递。需要泛型推断时,显式使用。
getIt()getIt<Foo>()dart
getIt.registerSingleton<RecipeRepository>(
MockRecipeRepositoryImpl(
recipeDataSource: getIt(),
),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());UseCase (싱글톤)
UseCase(单例)
상태를 갖지 않는 순수 기능이므로 싱글톤으로 충분하다. 타입 파라미터는 생략해도 된다 (구현 클래스가 하나뿐이므로 Dart 추론이 된다).
dart
getIt.registerSingleton(
GetSavedRecipesUseCase(
recipeRepository: getIt(),
bookmarkRepository: getIt(),
),
);UseCase是无状态的纯功能模块,使用单例即可。类型参数可省略(因仅存在一个实现类,Dart可自动推断)。
dart
getIt.registerSingleton(
GetSavedRecipesUseCase(
recipeRepository: getIt(),
bookmarkRepository: getIt(),
),
);ViewModel (팩토리)
ViewModel(工厂)
반드시 를 쓴다. 화면을 다시 열 때 새 인스턴스가 필요하기 때문이다.
registerFactorydart
getIt.registerFactory<IngredientViewModel>(
() => IngredientViewModel(
ingredientRepository: getIt(),
procedureRepository: getIt(),
getDishesByCategoryUseCase: getIt(),
clipboardService: getIt(),
),
);必须使用。因为重新打开页面时需要新的实例。
registerFactorydart
getIt.registerFactory<IngredientViewModel>(
() => IngredientViewModel(
ingredientRepository: getIt(),
procedureRepository: getIt(),
getDishesByCategoryUseCase: getIt(),
clipboardService: getIt(),
),
);Root 위젯에서 주입하기
在Root组件中注入
dart
class SavedRecipesRoot extends StatelessWidget {
const SavedRecipesRoot({super.key});
Widget build(BuildContext context) {
final viewModel = getIt<SavedRecipesViewModel>();
return ListenableBuilder(
listenable: viewModel,
builder: (context, _) => SavedRecipesScreen(
recipes: viewModel.state.recipes,
onAction: viewModel.onAction,
),
);
}
}원칙: 호출은 위젯의 안에서만 일어난다. Screen 위젯은 와 만 받도록 유지해 프리뷰/테스트가 가능하게 한다.
getIt<>()*Rootbuild()stateonActiondart
class SavedRecipesRoot extends StatelessWidget {
const SavedRecipesRoot({super.key});
Widget build(BuildContext context) {
final viewModel = getIt<SavedRecipesViewModel>();
return ListenableBuilder(
listenable: viewModel,
builder: (context, _) => SavedRecipesScreen(
recipes: viewModel.state.recipes,
onAction: viewModel.onAction,
),
);
}
}原则:仅在组件的方法内调用。Screen组件仅接收和,确保其可预览、可测试。
getIt<>()*Rootbuild()stateonAction싱글톤 vs 팩토리
单例 vs 工厂
| 등록 함수 | 언제 쓰나 | 예 |
|---|---|---|
| 앱 수명 동안 한 인스턴스만 필요. 이미 생성한 객체를 바로 넣을 때 | Repository, DataSource, UseCase, HttpClient |
| 최초 접근 시점까지 생성을 늦추고 싶을 때 | 초기화 비용이 큰 객체 |
| 호출할 때마다 새 인스턴스 | ViewModel |
이 프로젝트의 실제 예()에서는 Repository/DataSource/UseCase는 , ViewModel은 로 일관되게 분리되어 있다. 이 규칙을 따르라.
lib/core/di/di_setup.dartregisterSingletonregisterFactory| 注册函数 | 使用场景 | 示例 |
|---|---|---|
| 应用生命周期内仅需一个实例,直接传入已创建的对象时 | Repository、DataSource、UseCase、HttpClient |
| 希望延迟到首次访问时再创建实例时 | 初始化成本较高的对象 |
| 每次调用都需要新实例时 | ViewModel |
本项目的实际示例()中,Repository/DataSource/UseCase统一使用,ViewModel统一使用,请遵循此规则。
lib/core/di/di_setup.dartregisterSingletonregisterFactory팩토리 메서드가 필요한 경우
需要工厂方法的情况
생성자 하나로 끝나지 않고 팩토리 메서드를 호출해야 한다면 람다 폼을 쓴다.
dart
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));Named 파라미터가 필요한 경우도 동일하게 람다 폼을 사용한다.
若无法通过单一构造函数完成初始化,需调用工厂方法时,使用lambda形式。
dart
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));需要命名参数时,同样使用lambda形式。
체크리스트 — 새 feature 추가 시 DI 등록
检查清单 — 添加新功能模块时的DI注册
- DataSource 인터페이스가 또는
domain에 정의되어 있다data/data_source/ - 구현체를 로 등록 (인터페이스 타입 명시)
registerSingleton<Interface> - Repository 인터페이스/구현 등록
- UseCase 등록 (상태 없으면 싱글톤)
- ViewModel은 항상
registerFactory - Root 위젯에서만 로 꺼냄
getIt<ViewModelType>()
- DataSource接口已定义在或
domain目录下data/data_source/ - 实现类已通过注册(显式指定接口类型)
registerSingleton<Interface> - 已完成Repository接口/实现的注册
- 已完成UseCase注册(无状态则使用单例)
- ViewModel**始终使用**注册
registerFactory - 仅在Root组件中通过获取实例
getIt<ViewModelType>()
안티 패턴
反模式
- ❌ 를 Screen 위젯, 공용 위젯, StatelessWidget 트리 깊숙이에서 호출 → 테스트와 프리뷰가 불가능해진다.
getIt<T>() - ❌ ViewModel을 으로 등록 → 화면 재진입 시 상태가 오염된다.
registerSingleton - ❌ 밖에서
diSetup()를 몰래 호출 → 등록 순서와 의존 그래프가 흩어진다.getIt.register* - ❌ 구현 클래스 타입으로 에서 꺼내기 (
getIt) → 인터페이스 타입으로 꺼내 구현 교체가 가능하게 하라.getIt<MockRecipeRepositoryImpl>()
- ❌ 在Screen组件、公共组件、StatelessWidget树的深层调用→ 会导致无法测试和预览。
getIt<T>() - ❌ 使用注册ViewModel → 重新进入页面时状态会被污染。
registerSingleton - ❌ 在之外偷偷调用
diSetup()→ 会导致注册顺序和依赖图混乱。getIt.register* - ❌ 通过实现类类型从获取实例(
getIt) → 应通过接口类型获取,确保可替换实现类。getIt<MockRecipeRepositoryImpl>()