代码之家  ›  专栏  ›  技术社区  ›  Rik

为什么ref.watch(otherProvider.stream)在Riverpod 2.x中被弃用,并将在3.x中被删除?

  •  0
  • Rik  · 技术社区  · 1 年前

    我正在使用Riverpod收听对我商店中选定商品的更改:

    1. 我有一个存储库 PlacesStore 它有一个 Stream<PlaceDetails?> watchPlace(PlaceId placeId) 方法
    2. 我已经把这个包裹在一个家庭提供者里了,就像这样:
      @riverpod
      Stream<PlaceDetails?> place(PlaceRef ref, InternalPlaceId placeId) {
          final store = ref.watch(placesStoreProvider);
          return store.watchPlace(placeId);
      }
      
    3. 我有一个单独的 StateProvider , selectedPlaceIdProvider ,其被更新为用户在地图上选择的地点的地点ID。
    4. 我有另一个提供者, selectedPlaceProvider ,将两者结合在一起,如下所示:
      @riverpod
      Stream<PlaceDetails?> selectedPlace(SelectedPlaceRef ref) {
          final placeId = ref.watch(selectedPlaceIdProvider);
          if (placeId != null) {
              return ref.watch(placeProvider(placeId).stream);
          }
      
          return const Stream.empty();
       }
      

    我之所以这么做,是因为我有很多小部件想知道用户选择了什么地方,所以我认为把它拉到一个包含当前选择的地方详细信息的提供者中是有意义的(同时仍然保留家庭提供者,以便可以查找特定的地方)。

    但是,当使用 ref.watch(placeProvider(placeId).stream) 有人警告我 .stream 已弃用。我不太明白我应该用什么来代替它。

    以上模式有效吗?如果是,我该替换什么 流动 这样我仍然可以收听对当前选定位置的任何更改。

    0 回复  |  直到 1 年前
        1
  •  5
  •   Rémi Rousselet    1 年前

    这个 .stream 修改器一直被弃用,因为它几乎总是使用不当,导致了复杂的难以发现的错误。

    你在这个问题中给出的片段也面临着同样的问题。您提供的片段有一个错误:

    @riverpod
    Stream<PlaceDetails?> selectedPlace(SelectedPlaceRef ref) {
        final placeId = ref.watch(selectedPlaceIdProvider);
        if (placeId != null) {
            return ref.watch(placeProvider(placeId).stream);
        }
    
        return const Stream.empty();
    }
    

    在这个片段中 流动 引入了一个问题,现在 selectedPlace 取决于读取提供程序的顺序。

    假设你做到了:

    void main() async {
      final container = ProviderContainer();
      final place = await container.read(placeProvider(123).future);
    
      container.read(selectedPlaceIdProvider.notifier).state = 123;
      container.listen(selectedPlaceProvider, (_, value) {
        print('Place $value');
      });
    }
    

    然后在这种情况下,您会发现 print 事实上从未达到,并且 选定的位置 从不发射任何东西。
    但如果你要评论 final place = await container.read(placeProvider(123).future); 行,则您的代码将“工作”。

    这实际上意味着使用 流动 引入了比赛条件。除非您熟悉这个问题,否则很难发现它,并且可能会导致严重的可维护性问题。

    另一方面 流动 不会给Riverpod增加太多价值。你已经可以不用 流动

    引入弃用的变更日志为您的使用提供了替代语法 https://github.com/rrousselGit/riverpod/blob/master/packages/riverpod/CHANGELOG.md#230

    在您的情况下,您可以重构 selectedPlaceProvider 收件人:

    @riverpod
    Future<PlaceDetails?> selectedPlace(SelectedPlaceRef ref) {
        final placeId = ref.watch(selectedPlaceIdProvider);
        if (placeId == null) return null;
    
        return ref.watch(placeProvider(placeId).future);
    }
    

    此代码段的行为与您期望的.stream变体的行为类似。在该场景中, 选定的位置 将随时更新 placeProvider selectedPlaceIdProvider 使现代化
    但同时,这并不涉及上面讨论的种族条件问题。那个 main 以前写的内容现在可以按预期工作了。

        2
  •  2
  •   Randal Schwartz    1 年前

    你真的不需要来自提供者的流。如果希望视图或提供程序依赖于其他提供程序更改,只需使用 ref.watch ref.listen

    流在状态管理中没有位置。流对于记录每一个中间状态都很重要的地方很有用,比如日志或银行交易分类账。但在国家管理中 最近的 价值才是最重要的。你只需要你的视图或你的提供者来消费另一个提供者的最新价值,你就完成了。

    .stream 被删除,因为它使代码的其他部分变得困难。如果你真的需要一个流,你可以监听提供者,并使用你的规则生成你自己的流。