添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
茫然的猴子  ·  BufferedReader (Java ...·  2 周前    · 
体贴的风衣  ·  SQL Server connection ...·  3 月前    · 
有情有义的大象  ·  884549 – ...·  9 月前    · 
刀枪不入的马铃薯  ·  oracle ...·  1 年前    · 
跳到主要内容
?
?

读取 Provider

在阅读本指南之前,请确保先阅读有关 Providers 的内容。

在本指南中,我们将了解如何使用provider。

获取一个“ref”对象

首先,也是最重要的,在读取provider之前,我们需要获取一个“ref”对象。

这个对象能够让我们与provider交互,不管是来自widget还是其他provider。

从provider获取“ref”

所有provider都接收一个“ref”作为参数:



String value(ValueRef ref) {
// 使用ref获取其他provider
final repository = ref.watch(repositoryProvider);
return repository.get();
}

将此参数传递给provider暴露的值是安全的。



class Counter extends _$Counter {

int build() => 0;

void increment() {
// Counter可以使用“ref”读取其他provider
final repository = ref.read(repositoryProvider);
repository.post('...');
}
}

这样做允许 Counter 类读取provider。

从widget获取“ref”

Widget自然没有ref参数。但是 Riverpod 提供了多种从widget中获取ref的解决方案。

扩展ConsumerWidget而不是StatelessWidget

在widget树中获取ref的最常用方法是将 StatelessWidget 替换为 ConsumerWidget

ConsumerWidget 在用法上与 StatelessWidget 相同,唯一的区别是它在构建方法上有一个额外的参数:“ref”对象。

一个典型的 ConsumerWidget 如下所示:


class HomeView extends ConsumerWidget {
const HomeView({super.key});


Widget build(BuildContext context, WidgetRef ref) {
// 使用ref监听provider
final counter = ref.watch(counterProvider);
return Text('$counter');
}
}

扩展ConsumerStatefulWidget+ConsumerState而不是StatefulWidget+State

ConsumerWidget 类似, ConsumerStatefulWidget ConsumerState 等价于带有状态的StatefulWidget,区别在于状态中有一个“ref”对象。

这一次,“ref”没有作为构建方法的参数传递,而是作为 ConsumerState 对象的属性传递:


class HomeView extends ConsumerStatefulWidget {
const HomeView({super.key});


HomeViewState createState() => HomeViewState();
}

class HomeViewState extends ConsumerState<HomeView> {

void initState() {
super.initState();
// “ref” 可以在 StatefulWidget 的所有的生命周期内使用。
ref.read(counterProvider);
}


Widget build(BuildContext context) {
// 我们也可以在build函数中使用“ref”监听provider
final counter = ref.watch(counterProvider);
return Text('$counter');
}
}

使用ref与provider交互

现在我们有了一个“ref”,我们可以开始使用它了。

“ref”有三种主要用法:

  • 获取provider的值并监听更改,这样当该值发生更改时, 将重新构建订阅该值的widget或provider。
    这是使用 ref.watch 完成的
  • 在provider上添加监听器,以执行诸如导航到新页面或每当provider更改时显示模态框等操作。
    这是使用 ref.listen 完成的。
  • 在忽略更改的情况下获取provider的值。 当我们在诸如“on click”之类的事件中需要provider的值时很有用。
    这是使用 ref.read 完成的。
备注

尽可能使用 ref.watch 而不是 ref.read ref.listen 来实现你的功能。 通过依赖 ref.watch ,你的应用变得既具有响应性又具有声明性,这使得项目会更易于维护。

Using ref.watch to observe a provider

使用 ref.watch 观察provider

Ref.watch在widget的 build 方法中或在provider的主体中使用,以使widget/provider监听provider:

例如,一个provider可以使用 ref.watch 将多个provider组合成一个新值。

筛选待办清单就是一个例子。我们可以有两个provider:

  • filterTypeProvider ,一个能够暴露当前过滤器类型(不显示,只显示完成的内容等等)的provider。
  • todosProvider ,暴露整个待办清单列表的provider。

通过 ref.watch ,我们可以创建第三个provider, 它结合了这两个provider来创建一个过滤过的待办清单列表:



FilterType filterType(FilterTypeRef ref) {
return FilterType.none;
}


class Todos extends _$Todos {

List<Todo> build() {
return [];
}
}


List<Todo> filteredTodoList(FilteredTodoListRef ref) {
// 获取筛选器和待办清单列表
final FilterType filter = ref.watch(filterTypeProvider);
final List<Todo> todos = ref.watch(todosProvider);

switch (filter) {
case FilterType.completed:
// 返回完成的待办清单
return todos.where((todo) => todo.isCompleted).toList();
case FilterType.none:
// 返回所有的待办清单
return todos;
}
}

有了这段代码, filteredTodoListProvider 现在暴露了过滤后的清单列表。

如果筛选器或待办清单列表发生变化,筛选后的列表也会自动更新。 同时,如果过滤器和待办清单列表都没有改变,则不会重新计算那个列表。

类似地,widget可以使用ref.watch显示来自provider的内容, 并在内容发生变化时更新用户界面:



int counter(CounterRef ref) => 0;

class HomeView extends ConsumerWidget {
const HomeView({super.key});


Widget build(BuildContext context, WidgetRef ref) {
// 使用ref监听provider
final counter = ref.watch(counterProvider);

return Text('$counter');
}
}

这个代码段显示了一个widget,它监听了存储 count 的provider。 如果该 count 发生变化,widget将重新构建,UI将更新以显示新的值。

警告

像在 ElevatedButton onPressed 中那样, watch 方法不应该被异步调用。 它也不应该在 initState 和其他 State 的生命周期中使用。

在这种情况下,请考虑使用 ref.read

使用ref.listen来响应provider的变化

ref.watch 类似,也可以使用ref.listen来观察一个provider。

The main difference between them is that, rather than rebuilding the widget/provider if the listened to provider changes, using ref.listen will instead call a custom function. 它们之间的主要区别是,如果监听的provider发生更改, 使用 ref.listen 将调用自定义的函数,而不是重新构建widget/provider。

这对于在发生特定变化时执行操作很有用,例如在发生错误时显示snackbar。

ref.listen 方法需要两个位置参数,第一个是Provider,第二个是当状态改变时我们想要执行的回调函数。 当调用回调函数时将传递前一个状态的值和新状态的值。

ref.listen 方法可以在provider内部使用:



void another(AnotherRef ref) {
ref.listen<int>(counterProvider, (int? previousCount, int newCount) {
print('The counter changed $newCount');
});
// ...
}

或者在widget的 build 方法中:



class Counter extends _$Counter {

int build() => 0;
}

class HomeView extends ConsumerWidget {
const HomeView({super.key});


Widget build(BuildContext context, WidgetRef ref) {
ref.listen<int>(counterProvider, (int? previousCount, int newCount) {
print('The counter changed $newCount');
});

return Container();
}
}
警告

像在 ElevatedButton onPressed 中那样, listen 方法不应该被异步调用。 它也不应该在 initState 和其他 State 的生命周期中使用。

使用ref.read获取一个provider的状态

ref.read 是一种不监听provider状态的方法。

它通常在用户交互触发的函数中使用。 例如,我们可以使用 ref.read 在用户单击按钮时将计数器数值加1:



class Counter extends _$Counter {

int build() => 0;
void increment() => state = state + 1;
}

class HomeView extends ConsumerWidget {
const HomeView({super.key});


Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// 对“Counter”类调用“increment()”方法
ref.read(counterProvider.notifier).increment();
},
),
);
}
}
备注

你应该尽量避免使用 ref.read ,因为它不是响应式的。

它的存在是由于使用 watch listen 会导致问题。如果可以,使用 watch / listen 更好,尤其是 watch

不要 在build方法中使用 ref.read

你可能会想使用 ref.read 来优化widget的性能:



class Counter extends _$Counter {

int build() => 0;
void increment() => state = state + 1;
}

Widget build(BuildContext context, WidgetRef ref) {
// 使用 “read” 忽略provider的更新
final counter = ref.read(counterProvider.notifier);
return ElevatedButton(
onPressed: counter.increment,
child: const Text('button'),
);
}

但这样做是非常糟糕的,它可能会导致预料之外的bug。

以这种方式使用 ref.read 通常会让人觉得“provider暴露的值永远不会改变, 所以使用 ref.read 是安全的”。但问题是, 虽然现在的provider可能确实永远不会更新它的值,但你无法保证以后的值还是一样的。

应用往往会发生很多变更,假设在未来,以前从未改变的一个值将需要改变。 如果你使用 ref.read ,当该值需要更改时,你必须遍历整个代码库将 ref.read 更改为 ref.watch , 这很容易出错,而且你很可能会忘记某些情况。

但如果一开始就使用 ref.watch ,当你重构时遇到的问题就会更少。

但是我想要用 ref.read 来减少小部件重新构建的次数

这个想法很好,但需要注意的是,使用 ref.watch 也可以达到完全相同的效果(减少重新构建的次数)。

provider提供了许多获取值的方法,同时也减少了重新构建的次数,你可以使用这些方法。

比如,不应该这样:



class Counter extends _$Counter {

int build() => 0;
void increment() => state = state + 1;
}

Widget build(BuildContext context, WidgetRef ref) {
Counter counter = ref.read(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('button'),
);
}

我们应该:



class Counter extends _$Counter {

int build() => 0;
void increment() => state = state + 1;
}

Widget build(BuildContext context, WidgetRef ref) {
Counter counter = ref.watch(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('button'),
);
}

这两段代码实现了相同的效果:当计数器增加时,我们的按钮也不会重新构建。

另一方面,第二种方法支持重置计数器。例如,应用的另一部分可以调用:

ref.refresh(counterProvider);

来重新创建 Counter 对象。

如果我们在这里使用 ref.read ,我们的按钮仍将使用之前的 Counter 实例, 但实际上该实例已被丢弃,不应该再使用。 而正确使用 ref.watch 将重新构建按钮以使用新的 Counter 实例。

选择读取的方式

根据你想要监听的provider,你可能有多个可以监听的值。

比如,考虑以下 StreamProvider

final userProvider = StreamProvider<User>(...);

当读取这个 userProvider 时,你可以:

  • 通过监听 userProvider 本身 同步读取当前状态:

    Widget build(BuildContext context, WidgetRef ref) {
    AsyncValue<User> user = ref.watch(userProvider);

    return user.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stack) => const Text('Oops'),
    data: (user) => Text(user.name),
    );
    }
  • 通过监听 userProvider.stream 获取关联的 Stream

    Widget build(BuildContext context, WidgetRef ref) {
    Stream<User> user = ref.watch(userProvider.stream);
    }
  • 通过监听 userProvider.future ,获得一个解析最新值的 Future

    Widget build(BuildContext context, WidgetRef ref) {
    Future<User> user = ref.watch(userProvider.future);
    }

不同的provider可能提供用法。
要了解更多信息,请阅读 API 参考 来获取每个provider的文档。

使用“select”来过滤重建内容

与读取provider相关的最后一个特性是能够减少widget/provider从 ref.watch 重新构建的次数, 或者减少 ref.listen 执行函数的频率。

记住,这一点很重要,因为默认情况下,监听provider将监听整个对象状态。