[官文翻译]Flutter状态管理库Riverpod - 所有的Provider - StateNotifierProvider

1,199 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情


Riverpod的官方文档有多国语言,但是没有汉语,所以个人简单翻译了一版。

官网文档:Riverpod

GitHub:GitHub - rrousselGit/river_pod

Pub:riverpod | Dart Package (flutter-io.cn)

译时版本:riverpod 1.0.3


StateNotifierProvider

StateNotifierProvider 是用来监听和暴露 StateNotifier (在 state_notifier 包中,Riverpod 又将其重新导出) 的 provider 。

和 StateNotifier 一起使用的 StateNotifierProvider 是 Riverpod 建议的管理状态的解决方案,这可能会改变用户交互的响应。

这通常用于:

  • 暴露 不可变的 状态,该状态会在自定义事件响应后跟着改变。
  • 集中逻辑用于在单个位置更改一些状态(又叫做 业务逻辑),不断改善可维护性。

作为一个用法示例,我们可以使用 StateNotifierProvider 实现 TODO 列表。这样做允许我们暴露如 addTodo 的方法使 UI 在用户交互时更新 TODO 列表:

// StateNotifier 的状态应该是不可改变的。
// 我们也可以使用如 Freezed 的包帮助我们实现。
@immutable
class Todo {
  const Todo({required this.id, required this.description, required this.completed});

  // 类中的所有属性都就该是 `final` 的。
  final String id;
  final String description;
  final bool completed;

  // 由于 Todo 是不可改变的,我们实现一个允许使用稍微不同内容克隆 TODO 的方法。
  Todo copyWith({String? id, String? description, bool? completed}) {
    return Todo(
      id: id ?? this.id,
      description: description ?? this.description,
      completed: completed ?? this.completed,
    );
  }
}

// StateNotifier 类会被传递给 StateNotifierProvider 。
// 这个类不应该对外暴露它的 “state” 属性,这意味着没有 public 的 getter/属性 !
// 用于该类的 public 的方法 会允许 UI 更改状态。
class TodosNotifier extends StateNotifier<List<Todo>> {
  // 我们初始化 TODO 列表为一个空列表
  TodosNotifier(): super([]);

  // 允许 UI 添加 TODO 。
  void addTodo(Todo todo) {
    // 因为我们的状态是不可改变的,所以不允许使用 `state.add(todo)` 。
    // 我们应该创建一个包含以前项目和新 TODO 项目的新列表。
    // 这里使用 Dart 的 spread 操作符比较有帮助!
    state = [...state, todo];
    // 不需要调用 "notifyListener" 或类似的方法。
    // 调用 "state =" 会在必要时自动重新构建 UI 。
  }

  // 允许移除 TODO
  void removeTodo(String todoId) {
    // 同样,我们的状态是不可改变的。所以我们会创建一个新列表代替现有的列表。
    state = [
      for (final todo in state)
        if (todo.id != todoId) todo,
    ];
  }

  // 标记 TODO 为完成
  void toggle(String todoId) {
    state = [
      for (final todo in state)
        // 我们只将匹配的 TODO 标记为完成
        if (todo.id == todoId)
          // 再说一次,因为我们的状态是不可改变的,我们需要复制该 TODO。
          // 我们使用前面实现的 `copyWith` 方法帮忙做该处理。
          todo.copyWith(completed: !todo.completed)
        else
          // 其它的 TODO 没有改变。
          todo,
    ];
  }
}

// 最后,我们使用 StateNotifierProvider 允许 UI 响应 TodosNotifier 类。
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
  return TodosNotifier();
});

现在我们已经定义了一个 StateNotifierProvider ,可以使用它在 UI 中和 TODO 列表交互:

class TodoListView extends ConsumerWidget {
  const TodoListView({Key? key}): super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // TODO 列表改变时重新构建组件
    List<Todo> todos = ref.watch(todosProvider);

    // 在可滚动的 ListView 中渲染 TODO 
    return ListView(
      children: [
        for (final todo in todos)
          CheckboxListTile(
            value: todo.completed,
            // 点击 TODO 时,改变它的 status
            onChanged: (value) => ref.read(todosProvider.notifier).toggle(todo.id),
            title: Text(todo.description),
          ),
      ],
    );
  }
}