各位,又到了我们原理篇的时间了,基于最近几篇flutter原理篇的文章,这一期应该是写paint重绘篇的内容的,但是最近在调试程序的时候遇到一个小的问题就是关于didChangeDependencies 方法什么时候被调用的有点搞不清楚,本着搞不清楚就上网查询的习惯,结果搜了好多篇文章发现要么就是内容非常简单说了和没说一样,要么就是写了一个Demo得出了一个没有细节经不住拷问的大概的结论(你也不能说他错,总觉得还是差了点什么),更有甚者就是千篇一律的拷贝内容文章(我想你应该懂我的意思),本着考究探索的精神发现了这些文章以后不惊要大呼一声:
”!这样下去是不对的。“
自己必须要搞清楚写出来才算对得起大家,也对得起自己在flutter上面的探索,于是就有了今天这篇文章,好了废话不多说了,让我们进入正题来讨论下 《didChangeDependencies方法到底是什么时候被调用的》
其实在普通我们写简单例子的时候我可以一句话说清楚didChangeDependencies方法在什么时候被调用,那就是在它的 State 对象对应的 Element 被 createElement() 的时候并且被Mount的时候就会被调用,简单就是说当这个对应的 Element 被重新创建的时候就会被调用
你看到这里就会说不对啊,你这个结论和我写的Demo对不上啊,和网上一些结论也大相径庭啊,你先别着急先往下看,看看到底是怎么一回事
我先上一个Demo,这个Demo也就是网上面传的比较广的,我们就以这个来举例子说明网上的结论:“父级结构中的层级发生变化时didChangeDependencies被调用“ 这个结论为什么是不完整
import 'package:flutter/material.dart';
class TestDidChangeDependencies2 extends StatefulWidget {
@override
State createState() => TestDidChangeDependencies2State();
}
class TestDidChangeDependencies2State extends State {
bool bDependenciesShouldChange = true;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: bDependenciesShouldChange ?
Container(
height: 500,
alignment: Alignment.centerLeft,
child: C(child: B()),
)
: Container(
height: 500,
alignment: Alignment.centerLeft,
child: C(child: SizedBox(width: 20, height: 50, child: B())),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
)
);
}
void _incrementCounter(){
setState(() {
bDependenciesShouldChange = !bDependenciesShouldChange;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("AdidChangeDependencies");
}
}
class B extends StatefulWidget {
@override
State createState() => BState();
}
class BState extends State {
@override
Widget build(BuildContext context) {
return Center(child: Text("Test"));
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("BdidChangeDependencies");
}
}
class C extends StatefulWidget {
final Widget child;
C({Key? key, required this.child}) : super(key: key);
@override
State createState() => CState();
}
class CState extends State {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("CdidChangeDependencies");
}
}
class D extends StatelessWidget{
final Widget child_D;
D({required this.child_D});
@override
Widget build(BuildContext context) {
return child_D;
}
}
这个是网上流传的Demo,我把它调试为比较好可运行状态,大家可以来观察下输出,首先刚运行的时候就会打印如下:
I/flutter (20121): AdidChangeDependencies
I/flutter (20121): CdidChangeDependencies
I/flutter (20121): BdidChangeDependencies
看吧这里第一次就会打印这些 Satet 对应的 didChangeDependencies 函数呢,这是因为在一开始 Widget 对应的 Element 为空的情况下会运行如下代码
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
// When the type of a widget is changed between Stateful and Stateless via
// hot reload, the element tree will end up in a partially invalid state.
// That is, if the widget was a StatefulWidget and is now a StatelessWidget,
// then the element tree currently contains a StatefulElement that is incorrectly
// referencing a StatelessWidget (and likewise with StatelessElement).
//
// To avoid crashing due to type errors, we need to gently guide the invalid
// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
// returns false which prevents us from trying to update the existing element
// incorrectly.
//
// For the case where the widget becomes Stateful, we also need to avoid
// accessing `StatelessElement.widget` as the cast on the getter will
// cause a type error to be thrown. Here we avoid that by short-circuiting
// the `Widget.canUpdate` check once `hasSameSuperclass` is false.
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
//省略以下代码
}
在运行到 updateChild 的时候 child 会为空(因为从没运行过),所以会运行到 newChild = inflateWidget(newWidget, newSlot); 这一句代码,如下:
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
其中会运行到 final Element newChild = newWidget.createElement(); 这一句用对应的Widget去创建对应的Element,随后调用 newChild.mount(this, newSlot);
接下来到element.mount,他主要有两个运行分支流程:
- 一个是ComponentElement的,
- 一个是RenderObjectElement的,
我们最常见的就是 ComponentElement.mount 代码如下:
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
我可以给大家先说明一下 mount 这个函数其主要目的接着就把新建的子element挂载到了element树上,这个主要不是我们今天的内容所以不展开讲,想看细节的请大家关注我之前写的文章
RenderObjectElement.mount
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
此方法会创建一个RenderObject,之后就会把此RenderObject添加到RenderObject树上,想更清楚的话请大家关注我之前写的博客
ComponentElement.mount 里面会运行一个 _firstBuild 方法:
StatefulElement._firstBuild 如下:
@override
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts([
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
assert(() {
state._debugLifecycleState = _StateLifecycle.initialized;
return true;
}());
state.didChangeDependencies();
assert(() {
state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
super._firstBuild();
}
ComponentElement._firstBuild
void _firstBuild() {
rebuild();
}
这里面会调用 state.didChangeDependencies(); 这个方法,这也就是我们重写的State的didChangeDependencies方法
好了,我们搞清楚了第一次运行的时候 didChangeDependencies 方法的被调用的过程,现在我们来点击下一上面那个Demo的按钮,看看他又做了什么呢?
我们点击了按钮调用了 setState 方法,这个方法会使build方法运行导致C的child被改变了,改为了SizedBox:
body: bDependenciesShouldChange ?
Container(
height: 500,
alignment: Alignment.centerLeft,
child: C(child: B()),
)
: Container(
height: 500,
alignment: Alignment.centerLeft,
child: C(child: SizedBox(width: 20, height: 50, child: B())),
)
熟悉Build运行流程的同学应该知道,setState 这个方法会触发到 updateChild 这个方法,上面有代码我就不贴了,这个方法里面会进行一个判断,判断 hasSameSuperclass 变量是否为false,如果为false则会触发 inflateWidget 进行Element的重构,也就是我们上面分析的步骤,我这里给大家贴一个图会比较明显:
大家看看,点击了按钮以后导致 hasSameSuperclass 变量判断为false(意思是他们不是同一个父亲),之后导致inflateWidget(SizeBox,newSlot) , 之后再会导致 SizeBox 的child为null,再会触发B进行inflateWidget方法重构其对应的Element
逻辑部分都在 updateChild 函数里面:
if (child != null) {
bool hasSameSuperclass = true;
//省略部分代码
}else{
newChild = inflateWidget(newWidget, newSlot); //这里面的newWidget是B
}
所以这样就会触发BState 对应的 didChangeDependencies 方法
好了,到这里你应该就明白了为什么网上流传的结论 父级结构中的层级发生变化时didChangeDependencies被调用 这个结论的到底是怎么一回事了,其实这个会触发其对应的 Element重构 导致该方法被调用
好了,既然我们已经搞清楚了调用的规则,我们不妨再来看看还会不会有别的情况会触发呢,我们再来看看 updateChild 方法里面的逻辑判断:
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
//省略以下代码
}
我们看到 (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) 这一行说明如果他们的父亲没有变化,但是 Widget.canUpdate 的返回发生了变化的话,那么他应该也会触发下面的 newChild = inflateWidget(newWidget, newSlot); 方法导致 didChangeDependencies 方法被调用,我们看看这个方法:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
我们只需要改变他对应的key就可以实现我们的猜想了不是吗?
所以我列出了下面的例子说明改情况:
import 'package:flutter/material.dart';
class TestDidChangeDependencies3 extends StatefulWidget {
@override
State createState() => TestDidChangeDependencies3State();
}
class TestDidChangeDependencies3State extends State {
bool bDependenciesShouldChange = false;
late Key k;
@override
void initState() {
super.initState();
k = Key('hello');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
alignment: Alignment.centerLeft,
child: C(key:k,child: B()),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
)
);
}
void _incrementCounter(){
setState(() {
k = Key('world');
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("AdidChangeDependencies");
}
}
class B extends StatefulWidget {
@override
State createState() => BState();
}
class BState extends State {
@override
Widget build(BuildContext context) {
return Center(child: Text("Test"));
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("BdidChangeDependencies");
}
}
class C extends StatefulWidget {
final Widget child;
C({Key? key, required this.child}) : super(key: key);
@override
State createState() => CState();
}
class CState extends State {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("CdidChangeDependencies");
}
}
例子很简单不详细讲解了,我们点击了按钮导致了C的key发生了变化导致C重构其Element也间接导致了B重构了对应的Element导致了如下的打印
I/flutter (22761): AdidChangeDependencies
I/flutter (22761): CdidChangeDependencies
I/flutter (22761): BdidChangeDependencies
I/flutter (22761): CdidChangeDependencies
I/flutter (22761): BdidChangeDependencies
好了,这里我们又学到了在不改变父类结构的情况下,如果改变Key也会导致didChangeDependencies被调用,但是其本质还是我一开始总结的:那就是在它的 State 对象对应的 Element 被 createElement() 的时候并且被Mount的时候就会被调用,简单就是说当这个对应的 Element 被重新创建的时候就会被调用
好了看到这里你可能会松了一口气,总算明白了 didChangeDependencies 被调用背后的逻辑了,但是我要告诉你的是还没有完哦,这只是最普通的情况之一哦,还有一种更常见的情况那就是使用 InheritedWidget 的时候也会触发调用,这个是一个非常常用的控件,我们作为要深入flutter的开发者来说,不能只看简单的情况,这个控件也要必须搞清楚,那么在该文章的下半结我们来聊一聊 《InheritedWidget 以及对应的运行函数极其背后的逻辑》
我们先来看看概念:InheritedWidget是 Flutter 中非常重要的一个功能型组件,它提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!
State对象有一个didChangeDependencies回调,它会在“依赖”发生变化时被Flutter 框架调用。而这个“依赖”指的就是子 widget 是否使用了父 widget 中InheritedWidget的数据!如果使用了,则代表子 widget 有依赖;如果没有使用则代表没有依赖。
我们借用网上的Demo先举一个例子:
import 'package:flutter/material.dart';
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final int data; //需要在子树中共享的数据,保存点击次数
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
return old.data != data;
}
}
class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => __TestWidgetState();
}
class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget.of(context)!.data.toString());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}
class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();
}
class _InheritedWidgetTestRouteState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
ElevatedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}
我们点击一次会加1,并且会打印一次 Dependencies change
这种情况不像上面我们分析的改变结构的情况了,他是怎么回事呢?
我们来看看点击了按钮 _TestWidget 是怎么拿到数据的,如下:
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget.of(context)!.data.toString());
}
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
背后逻辑就在于 context.dependOnInheritedWidgetOfExactType
Element.dependOnInheritedWidgetOfExactType
@override
T? dependOnInheritedWidgetOfExactType({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
这里会从 _inheritedWidgets![T] 里面去取出对应的Element,那这个Element什么时候被添加进这个Map里面的呢,答案在于mount方法的调用逻辑:
Element.mount
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
//省略部分代码
_updateInheritance();
}
InheritedElement._updateInheritance
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap.from(incomingWidgets);
else
_inheritedWidgets = HashMap();
_inheritedWidgets![widget.runtimeType] = this; //注意这一句话
}
也就是说在InheritedElement被mount的时候会把自己添加进入这个Map _inheritedWidgets![widget.runtimeType] = this; ,所以在取的时候你可以拿到这个InheritedElement对象
接下来再看看 dependOnInheritedElement 这个函数做了什么呢?
Element.dependOnInheritedElement
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
很简单不细说,就是把_TestWidget对应的Element添加进了_dependents 这个Map里面,准备作为触发的时候使用
那什么时候触发呢,也就是在我们点击按钮以后会触发setState间接的触发InheritedElement的update方法更新的时候
InheritedElement.updated
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
ProxyElement.updated
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
InheritedElement.notifyClients
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent); //注意这一句
}
}
会取出dependent,也就是我们存的_TestWidget对应的Element,然后调用notifyDependent
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
StatefulElement.didChangeDependencies
@override
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
这里这一句很重要_didChangeDependencies = true; 这里的意思是把_TestWidget对应的Element的这个_didChangeDependencies属性变为true,等待_TestWidget对应的Element也就是StatefulElement对应的performRebuild方法调用的时候触发
StatefulElement.performRebuild
@override
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
这里很明显了state.didChangeDependencies();这一句被调用了,说明我们重载的didChangeDependencies方法就会被调用了
好了,到这里我们把 didChangeDependencies 方法什么时候被调用说得差不多了,但是大家还需要注意一个点就是 updateShouldNotify 这个函数的作用,这个函数网上说的是:是否通知子树中依赖的Widget调用其对应的 didChangeDependencies 方法,返回true则为通知,其实这里还有更深一层的含义在里面,就是这个返回值直接影响了依赖 InheritedWidget 对应对象的build方法是否执行
最后再仔细看看,确实如果InheritedWidget对应的updateShouldNotify方法返回为false的话,那么super.updated(oldWidget);方法将无法调用
InheritedElement.updated
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
InheritedElement继承至ProxyElement
ProxyElement.updated
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
InheritedElement.notifyClients
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
}
这里说得很清楚了,如果updateShouldNotify为false的话notifyDependent将不会被调用,那么State对应的didChangeDependencies也不会被调用
这里还没有完,接着往下看,notifyDependent会调用到 didChangeDependencies方法:
Element.didChangeDependencies
void didChangeDependencies() {
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
最终就不会到这里,也不会调用到依赖 InheritedWidget 对应对象的 markNeedsBuild 方法,所以他们的build方法无法被调用
ProxyElement.build
@override
Widget build() => widget.child;
究其原因在于ProxyElement对应的build方法,他这个方法与其他的build方法不一样,其他的build方法如下 Widget build() => state.build(this); 都会调用重写的build方法,他这个build只是返回widget.child,并没有触发build方法调用所以导致了他的build不具备构建的能力,依赖 InheritedWidget 对应对象要想更新就不能使updateShouldNotify返回false
这里是卡了我一个比较久的问题,当时确实想了半天才发现这里
好了,这篇文章说到这里已经差不多了,相信能读玩的小伙伴一定是具有一定耐心的
我也不在往下深究了,那不然结束不了了,总之今天我们讲了两个大致的内容:
- didChangeDependencies方法调用背后的逻辑
- InheritedWidget的基本使用
希望能给大家带来帮助,喜欢我的文章的话欢迎给我点赞或者留言,我看到的话一定第一时间给你回复的谢谢大家,你的点赞加留言是我持续更新的动力,谢谢大家···