美文网首页
[官文翻译]Flutter状态管理库Riverpod - 所有的

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

作者: 小城哇哇 | 来源:发表于2023-02-10 20:33 被阅读0次

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

官网文档:Riverpod

GitHub:GitHub - rrousselGit/river_pod

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

译时版本:riverpod 1.0.3


StateProvider

StateProvider 暴露了改变其状态的方式。 它是 StateNotifierProvider 的简化版, 设计用于很简单的使用场景下避免编写 StateNotifier 类。

StateProvider 的存在主要是允许通过 UI 对 简单 变量的更改。

StateProvider 的状态通常是:

  • 枚举,例如过滤类型
  • 字符串,通常是文件框(TextField之类)的原始内容
  • 逻辑变量,用于复选框
  • 数字,用于分页或表单中的年龄

下面的情况,不应该使用 StateProvider

  • 状态需要验证逻辑
  • 状态是复杂的对象(例如自定义类、列表/Map、等)
  • 更改状态的逻辑比简单的 count++ 高级很多。

更多高级的场景,考虑使用 StateNotifierProvider 来代替,并创建 StateNotifier 类。 对于工程的长期维护性来说,当初始的样板文件会有些大时,自定义 StateNotifier 类很危险 - 因为它在单个地方集中了状态的业务逻辑。

用法示例:使用下拉框改变过滤类型

StateProvider 的一个真实使用场景会是管理如下拉框/文本框/复选框之类的简单窗体组件。 特别是,我们会看到如何使用 StateProvider 实现允许改变商品列表如何排序的下拉框。

为了简化处理,商品列表会在应用里直接构建如下:

class Product {
  Product({required this.name, required this.price});

  final String name;
  final double price;
}

final _products = [
  Product(name: 'iPhone', price: 999),
  Product(name: 'cookie', price: 2),
  Product(name: 'ps5', price: 500),
];

final productsProvider = Provider<List<Product>>((ref) {
  return _products;
}); 

在真实的应用中,该列表通常会使用 FutureProvider 通过网络请求获取。

然后 UI 会如下显示商品列表:

Widget build(BuildContext context, WidgetRef ref) {
  final products = ref.watch(productsProvider);
  return Scaffold(
    body: ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return ListTile(
          title: Text(product.name),
          subtitle: Text('${product.price} $'),
        );
      },
    ),
  );
} 

现在我们完成了基本的处理,然后可以添加下拉框,它可以允许按价格或按名称过滤商品。 为了实现这个,我们会使用 DropDownButton

// 表现过滤类型的枚举
enum ProductSortType {
  name,
  price,
}

Widget build(BuildContext context, WidgetRef ref) {
  final products = ref.watch(productsProvider);
  return Scaffold(
    appBar: AppBar(
      title: const Text('Products'),
      actions: [
        DropdownButton<ProductSortType>(
          value: ProductSortType.price,
          onChanged: (value) {},
          items: const [
            DropdownMenuItem(
              value: ProductSortType.name,
              child: Icon(Icons.sort_by_alpha),
            ),
            DropdownMenuItem(
              value: ProductSortType.price,
              child: Icon(Icons.sort),
            ),
          ],
        ),
      ],
    ),
    body: ListView.builder(
      // ... 
    ),
  );
} 

现在有了下拉框,让我们创建一个 StateProvider 并用 provider 同步下拉框的状态。

首先,创建 StateProvider

final productSortTypeProvider = StateProvider<ProductSortType>(
  // 我们返回排序类型的默认值,这里是名称。
  (ref) => ProductSortType.name,
); 

然后,我们可以如下将 provider 和 下拉框连接:

DropdownButton<ProductSortType>(
  // 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
  value: ref.watch(productSortTypeProvider),
  // 当用户和下拉框交互时,我们更新 provider 的状态。
  onChanged: (value) =>
      ref.read(productSortTypeProvider.notifier).state = value!,
  items: [
    // ...
  ],
), 

这样,现在我们应该能改变排序类型了。尽管它还不会影响商品列表! 现在是最后一部分了:更新 productsProvider 排序商品列表。

实现该点的关键组件是使用 ref.watch,每当排序类型改变时,使 productsProvider 获取排序类型并重新计算商品列表。

该实现会是:

final productsProvider = Provider<List<Product>>((ref) {
  final sortType = ref.watch(productSortTypeProvider);
  switch (sortType) {
    case ProductSortType.name:
      return _products.sorted((a, b) => a.name.compareTo(b.name));
    case ProductSortType.price:
      return _products.sorted((a, b) => a.price.compareTo(b.price));
  }
}); 

这就是所有了!当排序类型改变时,对于 UI 自动重新渲染商品列表来说足够了。

Dartpad 上的完整示例:

// 该代码基于 MIT 许可证分发。
// Copyright (c) 2022 Remi Rousselet.
// 原始代码可在 https://github.com/rrousselGit/river_pod 找到。

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  }
}

class Product {
  Product({required this.name, required this.price});

  final String name;
  final double price;
}

final _products = [
  Product(name: 'iPhone', price: 999),
  Product(name: 'cookie', price: 2),
  Product(name: 'ps5', price: 500),
];

enum ProductSortType {
  name,
  price,
}

final productSortTypeProvider = StateProvider<ProductSortType>(
  // 我们返回排序类型的默认值,这里是名称。
  (ref) => ProductSortType.name,
);

final productsProvider = Provider<List<Product>>((ref) {
  final sortType = ref.watch(productSortTypeProvider);
  switch (sortType) {
    case ProductSortType.name:
      return _products.sorted((a, b) => a.name.compareTo(b.name));
    case ProductSortType.price:
      return _products.sorted((a, b) => a.price.compareTo(b.price));
  }
});

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final products = ref.watch(productsProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
        actions: [
          DropdownButton<ProductSortType>(
            // 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
            value: ref.watch(productSortTypeProvider),
            // 当用户和下拉框交互时,我们更新 provider 的状态。
            onChanged: (value) =>
                ref.read(productSortTypeProvider.notifier).state = value!,
            items: const [
              DropdownMenuItem(
                value: ProductSortType.name,
                child: Icon(Icons.sort_by_alpha),
              ),
              DropdownMenuItem(
                value: ProductSortType.price,
                child: Icon(Icons.sort),
              ),
            ],
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('${product.price} \$'),
          );
        },
      ),
    );
  }
} 

如何基于前一次的值更新状态时避免读取 provider 两次

有时候,想基于前一次的值更新 StateProvider 的状态。很自然地,你可以会如下去写:

final counterProvider = StateProvider<int>((ref) => 0);

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 我们从前一次的值更新状态,这样结束时会读取 provider 两次!
          ref.read(counterProvider.notifier).state = ref.read(counterProvider.notifier).state + 1;
        },
      ),
    );
  }
} 

该代码版本也没有什么特别错误,只是语法上有点不方便。

要使语法看上去更好些,可以使用 update 函数。 该函数会接收一个回调函数,该回调函数会接收当前状态然后期望结果是返回新的状态。

我们可以用它来重构前面的代码:

final counterProvider = StateProvider<int>((ref) => 0);

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(counterProvider.notifier).update((state) => state + 1);
        },
      ),
    );
  }
} 

该改动会达到同样的效果,也能使语法更好些。


最后

第一章 为什么 Flutter 是跨平台开发的终极之选

  • 这是为什么?
  • 跨平台开发
  • 什么是Flutter
  • Flutter特性
  • Flutter 构建应用的工具
  • 使用 Flutter 构建的热门应用
  • 构建 Flutter 应用的成本
image

第二章 在Windows上搭建Flutter开发环境

  • 使用镜像
  • 系统要求
  • 获取Flutter SDK
  • 编辑器设置
  • Android设置
  • 起步: 配置编辑器
  • 起步: 体验
  • 体验热重载
image

第三章 编写您的第一个 Flutter App

  • 创建 Flutter app
  • 使用外部包(package)
  • 添加一个 有状态的部件(Stateful widget)
  • 创建一个无限滚动ListView
  • 添加交互
  • 导航到新页面
  • 使用主题更改UI
image

第四章 Flutter开发环境搭建和调试

  • 开发环境的搭建
  • 模拟器的安装与调试
  • 开发环境的搭建
  • 模拟器的安装与调试
image

第五章 Dart语法篇之基础语法(一)

  • 简述
  • Hello Dart
  • 数据类型
  • 变量和常量
  • 集合(List、Set、Map)
  • 流程控制
  • 运算符
  • 异常
  • 函数
  • 总结
image

第六章 Dart语法篇之集合的使用与源码解析(二)

  • List
  • Set
  • Map
  • Queue
  • LinkedList
  • HashMap
  • Map、HashMap、LinkedHashMap、SplayTreeMap区别
  • 命名构造函数from和of的区别以及使用建议
image

第七章 Dart语法篇之集合操作符函数与源码分析(三)

  • 简述
  • Iterable
  • forEach
  • map
  • any
  • every
  • where
  • firstWhere和singleWhere和lastWhere
  • join
  • take
  • takeWhile
  • skip
  • skipWhile
  • follwedBy
  • expand
  • reduce
  • elementAt
image

第八章 Dart语法篇之函数的使用(四)

  • 简述
  • 函数参数
  • 匿名函数(闭包,lambda)
  • 箭头函数
  • 局部函数
  • 顶层函数和静态函数
  • main函数
  • Function函数对象
image

第九章 Dart语法篇之面向对象基础(五)

  • 简述
  • 属性访问器(accessor)函数setter和getter
  • 面向对象中的变量
  • 构造函数
  • 抽象方法、抽象类和接口
  • 类函数
  • 总结
image

第十章 Dart语法篇之面向对象继承和Mixins(六)

  • 简述
  • 类的单继承
  • 基于Mixins的多继承
  • 总结
image

第十一章 Dart语法篇之类型系统与泛型(七)

  • 简述
  • 可选类型
  • 接口类型
  • 泛型
  • 类型具体化
  • 总结
image

第十二章 Flutter中的widget

  • Flutter页面-基础Widget
  • Widget
  • StatelessWidget
  • State生命周期
  • 基础widget
  • DefaultTextStyle
  • FlutterLogo
  • Icon
  • Iamge.asset
  • CircleAvatar
  • FadeInImage
  • 按钮
  • FlatButton
  • OutlineButton
  • TextFormField
image

相关文章

网友评论

      本文标题:[官文翻译]Flutter状态管理库Riverpod - 所有的

      本文链接:https://www.haomeiwen.com/subject/dzokkdtx.html