热搜:前端 nest neovim nvim

Riverpod之StateProvider浅析(二)

lxf2023-05-25 01:04:24

在前一篇文章Riverpod之Provider(一)中,我们使用Provider讲解了WidgetRef和Ref的watch要点(如果没有看过,建议了解一下),因为上篇的知识点是本篇的基础,我们先简单回顾一下

WidgetRef的watch要点

  1. Provider的create方法初始化了状态值,存在ProviderElementBase中,也是watch返回的值
  2. ConsumerStatefulElement在ProviderElementBase中添加了一个 markNeedsBuild()监听,如果state发生变化,会通知监听者,也就是调用markNeedsBuild方法触发build Riverpod之StateProvider浅析(二)

Ref的watch要点

  1. Provider相互记录依赖的对象,蓝色块的靓仔通过_dependencies记录他依赖了谁,粉色块的美女通过_dependents记录谁依赖了她,后面状态发生变更,_dependents列表里面的对象会依次通知
  2. 获取粉色element的state值(初始值来自create方法) Riverpod之StateProvider浅析(二)

计数器Demo

计数器Demo来自官方的counter例子,每点击一次按钮,屏幕中央的文字加1

final counterProvider = StateProvider((ref) => 0);

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('example')),
      body: Center(
        child: Consumer(builder: (context, ref, _) {
          final count = ref.watch(counterProvider.state).state;
          return Text('$count');
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.state).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

核心代码如下,这次用的是StateProvider,用起来还是很简单的

final counterProvider = StateProvider((ref) => 0);

final count = ref.watch(counterProvider.state).state;

ref.read(counterProvider.state).state++;

watch流程

要注意的是这次读取的对象不是counterProvider,而是其中的state字段,这是一个内部使用的_NotifierStateProvider

  late final AlwaysAliveProviderBase<StateController<State>> state =
      _NotifierStateProvider(
    (ref) {
      return _listenStateProvider(
        ref as ProviderElementBase<StateController<State>>,
        ref.watch(notifier),
      );
    },
   ...
  );

根据之前的经验,要想知道watch返回什么,直接看它的create方法,代码没贴,我帮你看了,继承自Provider,那返回值就取决于它的Create参数了

State create(ProviderRef<State> ref) => _create(ref);

state的Create参数就比较复杂,内部还有一个watch方法,观察的是另一个名为notifier的_NotifierProvider,这种场景就是在上篇特意分析的在Provider中观察其它Provider,本来应该用其createElement方法返回值记录关系,但它们名字又长又没用,后面就用Provider名字替代了。

(ref) {
      return _listenStateProvider(
        ref as ProviderElementBase<StateController<State>>,
        ref.watch(notifier),
      );
    }

这里的ref就是state的createElement返回值ProviderElement,知道就行,叫什么名字不重要,因为基本都不干活。ref.watch(notifier)关注两点

  1. notifier的create方法,返回的是StateController,就是watch到的值,其中_create函数来自countProvider的Create函数“(ref) => 0”,那么初始化值为0
      StateController<State> create(StateProviderRef<State> ref) {
        final initialState = _create(ref);
        final notifier = StateController(initialState);
        ref.onDispose(notifier.dispose);
        return notifier;
      }

2. 建立了依赖关系,用provider名字表示就是下图这样 Riverpod之StateProvider浅析(二)

再看_listenStateProvider方法,就是给StateController添加一个监听,监听的实现就是ref.setState(controller),这里面的细节后面再说

StateController<State> _listenStateProvider<State>(
  ProviderElementBase<StateController<State>> ref,
  StateController<State> controller,
) {
  void listener(State newState) {
    ref.setState(controller);
  }

  // No need to remove the listener on dispose, since we are disposing the controller
  controller.addListener(listener, fireImmediately: false);

  return controller;
}

关于provider的一些数据整理如下表

Riverpod之StateProvider浅析(二)

用图表示关系如下,当ref.watch(counterProvider.state)方法返回时,我们知道返回值是StateController,其内部state为0,最后ref.watch(counterProvider.state).state返回值就是0,同时ref也在state中添加了重建监听,

Riverpod之StateProvider浅析(二)

StateController

从上面流程我们知道,我们操作的对象是StateController,为什么给它的state加1会引起页面变化,重点在于其继承自StateNotifier,StateNotifier的state更新时会通知监听者,我们一步步看这个流程是怎么流动起来的

    class StateController<T> extends StateNotifier<T> {

      StateController(T state) : super(state);

      @override
      T get state => super.state;

      @override
      set state(T value) => super.state = value;

      T update(T Function(T state) cb) => state = cb(state);
    }

其实读取后state++的那句话有好几步,它不是int类型的那种自增,那样的话是没有通知效果的

  1. 首先read返回的是StateController对象
  2. state++是先调用了getState方法,+1后调用了父类的setState方法
     ref.read(counterProvider.state).state++
     等于
     StateController controller=ref.read(counterProvider.state);
     int current=controller.getState
     super.setState(current+1)

3. StateNotifier的setState方法依次通知了监听者

    StateNotifier
     for (final listenerEntry in _listeners) {
          try {
            listenerEntry.listener(value);
          } catch (error, stackTrace) {
           ...
          }
        }

4. 前面说过监听的实现就是ref.setState(controller)

    ProviderElementBase
      void setState(State newState) {
      ...
        final previousState = getState();
        final result = _state = Result.data(newState);
        if (_didBuild) {
          _notifyListeners(result, previousState);
        }
      }

5. _notifyListeners方法很长,要通知的对象很多,当前关注这个listeners列表即可

        ProviderElementBase
         void _notifyListeners(Result<State> newState, Result<State>? previousStateResult,) {
          ...
            // 监听列表
            final listeners = _listeners.toList(growable: false);
            newState.map(
              data: (newState) {
                for (var i = 0; i < listeners.length; i++) {
                  Zone.current.runBinaryGuarded(
                    listeners[i]._listener,// 在这里markNeedsBuild()得到调用
                    previousState,
                    newState.state,
                  );
                }
               ...
          }

最后markNeedsBuild()方法的调用,会引起Consumer的build方法调用,watch方法会读到最新的state值。简要的关系图如下 Riverpod之StateProvider浅析(二)

这个Demo虽然涉及了Provider之间的交互,但notifier这个Provider没有参与进来,下一篇我们在Demo的基础上修改一番,展现Riverpod的奥义所在

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!