美文网首页
[官文翻译]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