You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dart/Flutter中复用带动态Model的无限滚动组件报错解决

如何在Dart泛型组件中调用Model的fromJSON静态方法?

问题原因

你遇到的错误是因为Dart的泛型参数ItemType只是一个类型标记,编译器无法确认它具有fromJSON静态方法,所以直接调用ItemType.fromJSON(map)会报错。

推荐解决方案

这里有两种更合理的方式解决这个问题,既能保证类型安全,又能实现组件复用:


方案1:定义抽象接口约束Model

先创建一个抽象类,强制所有需要复用的Model实现它的fromJSON工厂方法:

abstract class JsonParsable<T> {
  factory T.fromJSON(Map<String, dynamic> json);
}

让你的ArticleModel实现这个接口:

class ArticleModel implements JsonParsable<ArticleModel> {
  final String title;
  final String content;

  ArticleModel({required this.title, required this.content});

  @override
  factory ArticleModel.fromJSON(Map<String, dynamic> json) {
    return ArticleModel(
      title: json['title'],
      content: json['content'],
    );
  }
}

然后在InfiniteScroll组件中约束泛型必须是JsonParsable的子类:

class InfiniteScroll<ItemType extends JsonParsable<ItemType>> extends StatefulWidget {
  final String apiUrl;
  final Widget Function(BuildContext, ItemType, int) itemBuilder;

  const InfiniteScroll({
    Key? key,
    required this.apiUrl,
    required this.itemBuilder,
  }) : super(key: key);

  @override
  State<InfiniteScroll<ItemType>> createState() => _InfiniteScrollState<ItemType>();
}

class _InfiniteScrollState<ItemType extends JsonParsable<ItemType>> extends State<InfiniteScroll<ItemType>> {
  final PagingController<int, ItemType> _pagingController = PagingController(firstPageKey: 1);

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    super.initState();
  }

  Future<void> _fetchPage(int pageKey) async {
    try {
      final response = await http.get(Uri.parse('${widget.apiUrl}?page=$pageKey'));
      final data = jsonDecode(response.body);
      // 现在可以安全调用fromJSON
      final items = List<ItemType>.from(data['data'].map((map) => ItemType.fromJSON(map)));
      final isLastPage = items.length < 10; // 根据你的分页逻辑判断

      if (isLastPage) {
        _pagingController.appendLastPage(items);
      } else {
        _pagingController.appendPage(items, pageKey + 1);
      }
    } catch (error) {
      _pagingController.error = error;
    }
  }

  @override
  Widget build(BuildContext context) {
    return PagedListView<int, ItemType>(
      pagingController: _pagingController,
      builderDelegate: PagedChildBuilderDelegate<ItemType>(
        itemBuilder: widget.itemBuilder,
      ),
    );
  }
}

使用时直接传入ArticleModel即可:

InfiniteScroll<ArticleModel>(
  apiUrl: 'https://your-api.com/articles',
  itemBuilder: (context, article, index) {
    return ListTile(title: Text(article.title));
  },
)

方案2:传入解析函数(更灵活)

如果不想让所有Model都实现同一个接口,可以在InfiniteScroll组件中直接传入一个解析函数,这样自由度更高:

修改InfiniteScroll的构造函数,添加fromJson参数:

class InfiniteScroll<ItemType> extends StatefulWidget {
  final String apiUrl;
  final Widget Function(BuildContext, ItemType, int) itemBuilder;
  // 新增:接收Model的解析函数
  final ItemType Function(Map<String, dynamic>) fromJson;

  const InfiniteScroll({
    Key? key,
    required this.apiUrl,
    required this.itemBuilder,
    required this.fromJson,
  }) : super(key: key);

  @override
  State<InfiniteScroll<ItemType>> createState() => _InfiniteScrollState<ItemType>();
}

class _InfiniteScrollState<ItemType> extends State<InfiniteScroll<ItemType>> {
  final PagingController<int, ItemType> _pagingController = PagingController(firstPageKey: 1);

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    super.initState();
  }

  Future<void> _fetchPage(int pageKey) async {
    try {
      final response = await http.get(Uri.parse('${widget.apiUrl}?page=$pageKey'));
      final data = jsonDecode(response.body);
      // 使用传入的解析函数转换数据
      final items = List<ItemType>.from(data['data'].map((map) => widget.fromJson(map)));
      final isLastPage = items.length < 10;

      if (isLastPage) {
        _pagingController.appendLastPage(items);
      } else {
        _pagingController.appendPage(items, pageKey + 1);
      }
    } catch (error) {
      _pagingController.error = error;
    }
  }

  @override
  Widget build(BuildContext context) {
    return PagedListView<int, ItemType>(
      pagingController: _pagingController,
      builderDelegate: PagedChildBuilderDelegate<ItemType>(
        itemBuilder: widget.itemBuilder,
      ),
    );
  }
}

使用时传入ArticleModel.fromJSON作为解析函数:

InfiniteScroll<ArticleModel>(
  apiUrl: 'https://your-api.com/articles',
  fromJson: ArticleModel.fromJSON,
  itemBuilder: (context, article, index) {
    return ListTile(title: Text(article.title));
  },
)

关于你考虑的dynamic方案

把PagedListView泛型改为dynamic再在父组件转换的方式不推荐:

  • 丢失了Dart的类型安全检查,容易在运行时出现类型转换异常
  • 代码可读性差,后续维护时需要额外注意类型转换逻辑
  • 无法在编译阶段发现错误,增加调试成本

内容的提问来源于stack exchange,提问作者user259752

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:57:09