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

2023-02-10  本文已影响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 的状态通常是:

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

更多高级的场景,考虑使用 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 是跨平台开发的终极之选

image

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

image

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

image

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

image

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

image

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

image

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

image

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

image

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

image

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

image

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

image

第十二章 Flutter中的widget

image
上一篇下一篇

猜你喜欢

热点阅读