在前一篇文章Riverpod之Provider(一)中,我们使用Provider讲解了WidgetRef和Ref的watch要点(如果没有看过,建议了解一下),因为上篇的知识点是本篇的基础,我们先简单回顾一下
WidgetRef的watch要点
- Provider的create方法初始化了状态值,存在ProviderElementBase中,也是watch返回的值
- ConsumerStatefulElement在ProviderElementBase中添加了一个 markNeedsBuild()监听,如果state发生变化,会通知监听者,也就是调用markNeedsBuild方法触发build
Ref的watch要点
- Provider相互记录依赖的对象,蓝色块的靓仔通过_dependencies记录他依赖了谁,粉色块的美女通过_dependents记录谁依赖了她,后面状态发生变更,_dependents列表里面的对象会依次通知
- 获取粉色element的state值(初始值来自create方法)
计数器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)关注两点
- 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名字表示就是下图这样
再看_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的一些数据整理如下表
用图表示关系如下,当ref.watch(counterProvider.state)方法返回时,我们知道返回值是StateController,其内部state为0,最后ref.watch(counterProvider.state).state返回值就是0,同时ref也在state中添加了重建监听,
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类型的那种自增,那样的话是没有通知效果的
- 首先read返回的是StateController对象
- 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值。简要的关系图如下
这个Demo虽然涉及了Provider之间的交互,但notifier这个Provider没有参与进来,下一篇我们在Demo的基础上修改一番,展现Riverpod的奥义所在
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!