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

Riverpod如何维护列表选中项及索引并同步列表变更?

现有实现问题分析

你的实现已经跑通了核心逻辑,但存在几个可优化的问题:

  • 耦合度过高:NameListNotifier 中硬编码调用固定序号的 selectionProvider 更新方法,后续如果新增更多选中状态,必须修改列表类的代码,扩展性极差
  • 空列表判断逻辑错误:Dart 中列表的 == 比较的是内存引用,names == [] 永远不会返回 true,应该使用 names.isEmpty 判断
  • 手动触发更新不可靠:所有列表修改的入口都要手动调用选中状态的更新方法,一旦漏写就会出现状态不同步的问题
  • 没有用到 Riverpod 自带的监听能力,属于手动实现了状态依赖,不符合框架的最佳实践
优化方案

我们可以利用 Riverpod 的内置监听能力,让每个选中状态自动监听列表的变化,完全解耦列表和选中逻辑:

import 'package:riverpod/riverpod.dart';

void main(List<String> arguments) {
  final container = ProviderContainer();
  List<String> names = ["Jack", "Adam", "Sally"];
  container.read(nameListProvider.notifier).setNames(names);
  
  final firstSelection = container.read(selectionProvider(1).notifier);
  firstSelection.setByIndex(1);
  print('第一个选中项:${container.read(selectionProvider(1)).name},索引:${container.read(selectionProvider(1)).index}');
  
  final secondSelection = container.read(selectionProvider(2).notifier);
  secondSelection.setByIndex(2);
  print('第二个选中项:${container.read(selectionProvider(2)).name},索引:${container.read(selectionProvider(2)).index}');
  
  // 模拟列表变更
  names.remove('Adam');
  container.read(nameListProvider.notifier).setNames(List.from(names));
  
  print('列表更新后第一个选中项:${container.read(selectionProvider(1)).name},索引:${container.read(selectionProvider(1)).index}');
  print('列表更新后第二个选中项:${container.read(selectionProvider(2)).name},索引:${container.read(selectionProvider(2)).index}');
}

// 选中状态数据类,新增copyWith方便更新
class Selection {
  final String? name;
  final int? index;

  Selection({this.name, this.index});

  Selection copyWith({
    String? Function()? name,
    int? Function()? index,
  }) {
    return Selection(
      name: name != null ? name() : this.name,
      index: index != null ? index() : this.index,
    );
  }
}

// 选中状态Provider,用family区分不同的选中实例
final selectionProvider = StateNotifierProvider.family<SelectionNotifier, Selection, int>((ref, id) {
  return SelectionNotifier(ref);
});

class SelectionNotifier extends StateNotifier<Selection> {
  final Ref _ref;

  SelectionNotifier(this._ref) : super(Selection()) {
    // 核心:自动监听列表变化,列表更新时自动同步选中状态
    _ref.listen<List<String>>(nameListProvider, (previous, next) {
      _syncSelection();
    });
  }

  // 同步选中状态逻辑
  void _syncSelection() {
    final nameList = _ref.read(nameListProvider);
    // 列表为空直接重置
    if (nameList.isEmpty) {
      state = Selection();
      return;
    }
    // 没有选中记录默认选第一个
    final currentName = state.name;
    if (currentName == null) {
      state = Selection(name: nameList[0], index: 0);
      return;
    }
    // 查找当前选中名称的新索引
    final newIndex = nameList.indexOf(currentName);
    if (newIndex == -1) {
      // 已删除选第一个
      state = Selection(name: nameList[0], index: 0);
      return;
    }
    // 存在就更新索引
    state = state.copyWith(index: () => newIndex);
  }

  // 主动选中指定索引的元素
  void setByIndex(int index) {
    final nameList = _ref.read(nameListProvider);
    if (nameList.isEmpty || index < 0 || index >= nameList.length) return;
    state = Selection(name: nameList[index], index: index);
  }
}

// 列表状态Provider,不需要关心有多少个选中状态
final nameListProvider = StateNotifierProvider<NameListNotifier, List<String>>((ref) {
  return NameListNotifier();
});

class NameListNotifier extends StateNotifier<List<String>> {
  NameListNotifier() : super([]);

  void setNames(List<String> names) {
    state = List.unmodifiable(names); // 用不可变列表避免外部意外修改
  }
}
优化点说明
  • 完全解耦:列表状态和选中状态互不感知,新增选中状态只需要直接调用 selectionProvider(新id) 即可,不需要修改任何现有代码
  • 自动同步:依赖 Riverpod 的监听能力,列表变更后所有选中状态自动更新,不会出现漏更的问题
  • 鲁棒性提升:修复了空列表判断错误,新增了索引越界校验,列表使用不可变对象避免外部误改
  • 符合 Riverpod 最佳实践:使用 Ref 而非 Reader 存储引用,支持完整的框架能力

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 04:06:03