使用Injectable如何实现仅Web端生效的依赖注入?
Injectable的分环境注册只负责运行时的依赖实例选择,不会参与Dart的编译期源文件解析逻辑。只要WebImageDownloader所在文件被工程的导入链直接/间接引用,哪怕你没有给它配置当前环境的注册规则,非Web端编译时依然会解析这个文件里的所有import语句,碰到dart:html、dart:js这类Web专属库就会直接抛编译错误,和环境注册配置本身无关。
之前尝试条件导入不生效,基本都是因为存在路径直接引用了Web端实现文件,绕过了条件导入的隔离逻辑。
方案1:条件导入+实现文件隔离(最推荐,无额外配置)
把实现类和抽象类拆成独立文件,通过条件导入做编译期的文件隔离,从根源上避免非Web端加载Web专属代码:
- 新建统一导出入口文件
image_downloader.dart,定义抽象类并做条件导出:
abstract class ImageDownloader { Future<dynamic> download(String url, String savePath); } // 根据编译平台自动导出对应实现,非当前平台的实现文件不会被编译加载 export 'src/image_downloader_stub.dart' if (dart.library.html) 'src/image_downloader_web.dart' if (dart.library.io) 'src/image_downloader_mobile.dart';
- 分别在
src目录下创建三个文件:image_downloader_stub.dart:写兜底的空实现,避免条件匹配失败时报错image_downloader_web.dart:存放WebImageDownloader实现,可正常引入dart:html、dart:js,类上添加@Injectable(env: {Environment.web})注解image_downloader_mobile.dart:存放MobileImageDownloader实现,类上添加@Injectable(env: {Environment.mobile})注解
- 所有业务代码、Injectable的注入配置文件,只能import统一入口的
image_downloader.dart,绝对不能直接import src目录下的两个实现文件,否则条件导入的隔离逻辑会直接失效。
方案2:build.yaml配置编译期源文件排除(适合不想调整现有文件结构的场景)
如果不想改动现有导入逻辑,可以直接通过构建配置让非Web端编译时直接跳过Web专属实现文件:
在项目根目录的build.yaml中添加如下配置:
targets: $default: sources: exclude: - lib/src/implementation/web/**/*.dart - lib/**/*_web_impl.dart
把WebImageDownloader类放到上述配置的排除路径下,非Web构建时编译器完全不会扫描到该文件,自然不会解析它的Web专属依赖。注意Web构建时要调整配置把这些路径重新加入sources列表,否则Web端会找不到对应实现。
方案3:Module手动懒注册(备选,侵入性较高)
把Web端实现类放到独立的、不会被默认导入的路径下,通过Injectable的Module做条件懒注册:
@module abstract class RegisterModule { @Injectable(env: [Environment.mobile]) ImageDownloader get mobileDownloader => MobileImageDownloader(); @Injectable(env: [Environment.web]) ImageDownloader get webDownloader => _getWebDownloader(); }
其中_getWebDownloader方法通过条件导入引用Web实现,非Web端不会加载对应代码。这个方案需要手动写注册逻辑,维护成本更高,只作为备选。
踩坑提示:不要试图在
WebImageDownloader类内部加条件导入来规避问题,只要类本身所在文件被非Web端加载,哪怕类里的方法全是空实现,文件头部的dart:html导入依然会触发编译错误。
内容的提问来源于stack exchange,提问作者mirkancal

