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

Flutter:如何避免Provider变更时整个列表重构建

问题描述

我用Consumer配合Provider实现了ListView.builder,每个列表项需要异步加载内容,因此使用了FutureBuilder。但每次向列表添加项时,整个列表都会重构建,已有项明明未变更却要重新执行异步任务。

代码如下:

class ListWithButton extends StatelessWidget {
  const ListWithButton({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<MyProvider>(
      builder: (_, provider, __) => Column(
        children: [
          ElevatedButton(
            onPressed: () {
              provider.addItem();
            },
            child: const Text("Add Item"),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: provider.items.length,
              itemBuilder: (_, index) => FutureBuilder<Color>(
                future: provider.loadSomething(index),
                builder: (_, snapshot) => ListTile(
                  leading: Container(
                    width: 30,
                    height: 30,
                    color: snapshot.connectionState == ConnectionState.done
                        ? snapshot.data!
                        : Colors.red,
                  ),
                  title: Text(index.toString()),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class MyProvider extends ChangeNotifier {
  final List<int> _items = [];

  List<int> get items => _items;

  void addItem() {
    _items.add(_items.length);
    notifyListeners();
  }

  Future<Color> loadSomething(int i) async {
    await Future.delayed(Duration(seconds: 1));
    return Colors.green;
  }
}

请问有没有办法仅重构建ListView中需要更新的项(即新增的项)?


解决方案

要实现仅重构建新增列表项,避免已有项重复执行异步任务,可以通过以下方式调整:

1. 将列表项封装为独立的StatefulWidget

把每个列表项抽成单独的组件,在组件内部初始化异步请求,确保异步任务只在组件创建时执行一次,不受父组件重构建影响。

2. 缓存异步结果(可选优化)

在Provider中缓存已加载的结果,避免对同一列表项重复发起异步请求。

修改后的完整代码:

独立列表项组件

class AsyncListItem extends StatefulWidget {
  final int index;
  final MyProvider provider;

  const AsyncListItem({super.key, required this.index, required this.provider});

  @override
  State<AsyncListItem> createState() => _AsyncListItemState();
}

class _AsyncListItemState extends State<AsyncListItem> {
  late Future<Color> _loadFuture;

  @override
  void initState() {
    super.initState();
    // 仅在组件初始化时创建Future,避免每次build重复执行异步任务
    _loadFuture = widget.provider.loadSomething(widget.index);
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Color>(
      future: _loadFuture,
      builder: (_, snapshot) => ListTile(
        leading: Container(
          width: 30,
          height: 30,
          color: snapshot.connectionState == ConnectionState.done
              ? snapshot.data!
              : Colors.red,
        ),
        title: Text(widget.index.toString()),
      ),
    );
  }
}

主组件与优化后的Provider

class ListWithButton extends StatelessWidget {
  const ListWithButton({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<MyProvider>(
      builder: (_, provider, __) => Column(
        children: [
          ElevatedButton(
            onPressed: () {
              provider.addItem();
            },
            child: const Text("Add Item"),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: provider.items.length,
              itemBuilder: (_, index) => AsyncListItem(
                index: index,
                provider: provider,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class MyProvider extends ChangeNotifier {
  final List<int> _items = [];
  // 缓存已加载的颜色结果,避免重复请求
  final Map<int, Color> _cachedColors = {};

  List<int> get items => _items;

  void addItem() {
    _items.add(_items.length);
    notifyListeners();
  }

  Future<Color> loadSomething(int i) async {
    // 优先返回缓存结果
    if (_cachedColors.containsKey(i)) {
      return _cachedColors[i]!;
    }
    await Future.delayed(const Duration(seconds: 1));
    final color = Colors.green;
    _cachedColors[i] = color;
    return color;
  }
}

核心原理

  • 独立的AsyncListItem组件在initState中初始化异步Future,只会执行一次。即使父组件因Provider通知重构建,已有列表项的initState不会再次触发,因此不会重复执行异步任务。
  • ListView.builder本身是按需构建的,新增项时只会创建新的AsyncListItem实例,已有项不会被重新创建或重构建(即使滚动出屏幕后重新进入,缓存机制也能避免重复请求)。
  • 缓存机制进一步提升了性能,避免对同一索引的列表项重复发起异步请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 05:54:59