Riverpod: Правильно ли ссылаться в onPressed на провайдера, полученного ранее ref.watch?

Документы Riverpod говорят использовать в обратных вызовах, как кнопкуonPressed, так как вы не должны использоватьref.watchтам.

Можно ли делать что-то подобное?

      build(context, ref) {
  // Use ref.watch ahead of time, because I refer to p all over 
  // the widget tree. p is a mutable ChangeNotifier
  final p = ref.watch(myProvider);

  return ElevatedButton(
    onPressed: () {
      // Refer to provider obtained via ref.watch earlier
      p.mutateSomehow(),
    },
    child: /* huge tree referring to p 20 times */
  );
}

? Я делаю это во всем своем коде, и, похоже, он работает нормально, но если бы этот шаблон был в порядке, кажется, что Riverpod просто рекомендовал бы его и устарел.ref.read.

3 ответа

скажем, у вас есть провайдер, определенный следующим образом:final pProvider = StateProvider<int>((ref)=> 0);

и внутри вашей сборки вы смотрите его значение следующим образом:final p = ref.watch(pProvider.state).state;

теперь внутри вашей сборки хранится значение вашего провайдера.

внутри вашего обратного вызова, если вы хотите увеличить значениеp, вы можете сделать это, просто сказавp++илиp + 1, однако это означает, что вы просто увеличиваете полученное значение, а не состояние самого провайдера. это в основном сценарий, когда вы хотите получить значение провайдера, а затем добавить к нему что-то перед сохранением в базе данных.

С другой стороны, если вы хотите изменить состояние провайдера, за которым наблюдает вашref.watchметод, то вам нужно увеличить свой поставщик состояния, как этоref.read(pProvider.notifier).state++это гарантирует, что то, что вы увеличиваете, является состоянием поставщика, а не сохраненным значением. и это также вызывает перестройку вашего виджета, потому что отслеживаемое состояние меняется.

Надеюсь, это поможет объяснить вашу озабоченность, основываясь на том, что я понял.

Я думаю, это сделано для того, чтобы ГАРАНТИРОВАТЬ , что мы получаем самый актуальный статус провайдера. Поэтому следует использоватьперед любым действием в (){}. Так мы лишаем себя возможности ошибиться в будущем :)

И вот полный пример возможной ситуации:

      void main() => runApp(const ProviderScope(child: MyApp()));

final indexProvider = StateProvider<int>((ref) {
  ref.listenSelf((previous, next) {
    print(ref.controller.state);
  });
  return 0;
});

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $MyApp');

    return MaterialApp(
      home: Center(
        child: Column(
          children: const [
            SizedBox(height: 50.0),
            ButtonInternalState(),
            SizedBox(height: 50.0),
            ButtonProviderState(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $ButtonInternalState');

    // Once upon a time, all we had to do was read the data once and not keep track of anything
    final StateController<int> indexState = ref.read(indexProvider.state);

    return ElevatedButton(
      onPressed: () => indexState.state++,

      // It would have avoided the mistake
      // onPressed: () => ref.read(indexProvider.state).state++,

      child: Text('The state our wrong provider - ${indexState.state}'),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $ButtonProviderState');

    final int index = ref.watch(indexProvider);

    return Column(
      children: [
        ElevatedButton(
            onPressed: () => ref.read(indexProvider.notifier).state++,
            child: Text('Here changes state the provider - $index')),
        ElevatedButton(
            onPressed: () => ref.refresh(indexProvider),
            child: const Text('Refresh our provider')),
      ],
    );
  }
}

и отличаются:

Мы используемref.readчтобы получить значение поставщика один раз (одноразовое чтение).

Мы используемref.watchчтобы получить значение поставщика в первый раз и каждый раз, когда значение изменяется (наблюдайте за этим, как если бы вы подписались на поставщика, поэтому вы будете уведомлены об изменениях в любое время).

Вам не нужно всегда получать значение, вы должны получить значение поставщика только один раз.

Обновлять:

Я предлагаю вам использоватьpв другом месте и использоватьref.watch(myProvider).doSomething()вonPressedфункция.

Другие вопросы по тегам