Flutter : 상속 된 위젯을 올바르게 사용하는 방법?
InheritedWidget을 사용하는 올바른 방법은 무엇입니까? 지금까지 위젯 트리 아래로 데이터를 전파 할 수있는 기회를 제공한다는 것을 이해했습니다. 극단적으로 RootWidget으로 넣으면 모든 경로의 트리에있는 모든 위젯에서 액세스 할 수 있습니다. 어떻게 든 전역 또는 싱글 톤에 의존하지 않고 내 위젯에 대해 ViewModel / Model에 액세스 할 수 있도록해야하기 때문에 괜찮습니다.
그러나 InheritedWidget은 불변이므로 어떻게 업데이트 할 수 있습니까? 그리고 더 중요한 것은 내 Stateful 위젯이 하위 트리를 재 구축하기 위해 트리거되는 방법은 무엇입니까?
불행히도 문서는 여기에 매우 불분명하며 많은 토론 후 아무도 그것을 사용하는 올바른 방법을 실제로 알지 못하는 것 같습니다.
Brian Egan의 인용문을 추가합니다.
예, 데이터를 트리 아래로 전파하는 방법이라고 생각합니다. API 문서에서 헷갈리는 점 :
"상속 된 위젯은 이러한 방식으로 참조 될 때 상속 된 위젯 자체가 상태를 변경할 때 소비자가 다시 빌드하게합니다."
이 글을 처음 읽었을 때 다음과 같이 생각했습니다.
InheritedWidget에 일부 데이터를 채우고 나중에 변경할 수 있습니다. 해당 변형이 발생하면 InheritedWidget을 참조하는 모든 위젯을 다시 빌드합니다.
InheritedWidget의 상태를 변경하려면 StatefulWidget으로 래핑해야합니다. 그런 다음 실제로 StatefulWidget의 상태를 변경하고이 데이터를 InheritedWidget에 전달하면 데이터가 모든 자식에게 전달됩니다. 그러나이 경우 InheritedWidget을 참조하는 위젯뿐만 아니라 StatefulWidget 아래 전체 트리를 다시 빌드하는 것처럼 보입니다. 그 맞습니까? 아니면 updateShouldNotify가 false를 반환하는 경우 InheritedWidget을 참조하는 위젯을 건너 뛰는 방법을 알고 있습니까?
잘못된 견적에서 문제가 발생합니다.
말했듯이 InheritedWidget은 다른 위젯과 마찬가지로 변경 불가능합니다. 따라서 그들은 업데이트 하지 않습니다 . 새로 만들어집니다.
문제는 다음과 같습니다. InheritedWidget은 데이터를 보유하는 것 외에는 아무것도하지 않는 단순한 위젯입니다 . 업데이트 논리가 없습니다. 그러나 다른 위젯과 마찬가지로 Element
. 그리고 그거 알아? 이 것은 변경 가능하며 flutter는 가능할 때마다 재사용합니다!
수정 된 견적은 다음과 같습니다.
InheritedWidget이 이러한 방식으로 참조되면 InheritedElement에 연결된 InheritedWidget이 변경 될 때 소비자가 다시 빌드됩니다 .
widgets / elements / renderbox가 어떻게 연결되는지에 대한 좋은 이야기가 있습니다 . 그러나 간단히 말하면 다음과 같습니다 (왼쪽은 일반적인 위젯, 가운데는 '요소', 오른쪽은 '렌더 상자').
문제는 새 위젯을 인스턴스화 할 때입니다. flutter는 그것을 이전 것과 비교할 것입니다. RenderBox를 가리키는 "Element"를 재사용합니다. 그리고 RenderBox 속성을 변경 합니다.
좋아요,하지만 이것이 내 질문에 어떻게 대답합니까?
InheritedWidget을 인스턴스화 한 다음 호출 할 때 context.inheritedWidgetOfExactType
(또는 MyClass.of
기본적으로 동일 함); 함축 된 것은 Element
당신의 InheritedWidget
. 그리고 Element
새 위젯 을 가져올 때마다 이전 메서드를 호출 한 위젯을 강제로 새로 고칩니다.
요컨대, 기존의 InheritedWidget
것을 새로운 것으로 교체 할 때 ; flutter는 그것이 변경되었음을 볼 것입니다. 그리고 바인딩 된 위젯에 잠재적 인 수정 사항을 알립니다.
모든 것을 이해했다면 이미 해결책을 추측했을 것입니다.
무언가가 바뀔 때마다 새로운 것을 만들 수 있도록 InheritedWidget
내부를 감싸십시오 !StatefulWidget
InheritedWidget
실제 코드의 최종 결과는 다음과 같습니다.
class MyInherited extends StatefulWidget {
static MyInheritedData of(BuildContext context) =>
context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;
const MyInherited({Key key, this.child}) : super(key: key);
final Widget child;
@override
_MyInheritedState createState() => _MyInheritedState();
}
class _MyInheritedState extends State<MyInherited> {
String myField;
void onMyFieldChange(String newValue) {
setState(() {
myField = newValue;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedData(
myField: myField,
onMyFieldChange: onMyFieldChange,
child: widget.child,
);
}
}
class MyInheritedData extends InheritedWidget {
final String myField;
final ValueChanged<String> onMyFieldChange;
MyInheritedData({
Key key,
this.myField,
this.onMyFieldChange,
Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(MyInheritedData oldWidget) {
return oldWidget.myField != myField ||
oldWidget.onMyFieldChange != onMyFieldChange;
}
}
그러나 새 InheritedWidget을 만들면 전체 트리가 다시 작성되지 않습니까?
아니요, 반드시 그런 것은 아닙니다. 새로운 InheritedWidget은 잠재적으로 이전과 똑같은 자식을 가질 수 있습니다. 정확히 말하면 같은 경우를 의미합니다. 이전과 동일한 인스턴스를 가진 위젯은 다시 빌드하지 않습니다.
그리고 대부분의 상황 (앱 루트에 inheritedWidget이 있음)에서 상속 된 위젯은 constant 입니다. 따라서 불필요한 재 구축이 없습니다.
TL; DR
Don't use heavy computation inside updateShouldNotify method and use const instead of new when creating a widget
First of all, we should understand what is a Widget, Element and Render objects.
- Render objects are what is actually rendered on the screen. They are mutable, contain the painting and layout logic. The Render tree is very similar to the Document Object Model(DOM) in the web and you can look at a render object as a DOM node in this tree
- Widget - is a description of what should be rendered. They are immutable and cheap. So if a Widget answers the question "What?"(Declarative approach) then a Render object answer the question "How?"(Imperative approach). An analogy from the web is a "Virtual DOM".
- Element/BuildContext - is a proxy between Widget and Render objects. It contains information about the position of a widget in the tree* and how to update the Render object when a corresponding widget is changed.
Now we are ready to dive into InheritedWidget and BuildContext's method inheritFromWidgetOfExactType.
As an example I recommend we consider this example from Flutter's documentation about InheritedWidget:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
@override
bool updateShouldNotify(FrogColor old) {
return color != old.color;
}
}
InheritedWidget - just a widget which implements in our case one important method - updateShouldNotify. updateShouldNotify - a function which accepts one parameter oldWidget and returns a boolean value: true or false.
Like any widget, InheritedWidget has a corresponding Element object. It is InheritedElement. InheritedElement call updateShouldNotify on the widget every time we build a new widget(call setState on an ancestor). When updateShouldNotify returns true InheritedElement iterates through dependencies(?) and call method didChangeDependencies on it.
Where InheritedElement gets dependencies? Here we should look at inheritFromWidgetOfExactType method.
inheritFromWidgetOfExactType - This method defined in BuildContext and every Element implements BuildContext interface (Element == BuildContext). So every Element has this method.
Lets look at the code of inheritFromWidgetOfExactType:
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
Here we try to find an ancestor in _inheritedWidgets mapped by type. If the ancestor is found, we then call inheritFromElement.
The code for inheritFromElement:
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
- We add ancestor as a dependency of the current element (_dependencies.add(ancestor))
- We add current element to ancestor's dependencies (ancestor.updateDependencies(this, aspect))
- We return ancestor's widget as result of inheritFromWidgetOfExactType (return ancestor.widget)
So now we know where InheritedElement gets its dependencies.
Now lets look at didChangeDependencies method. Every Element has this method:
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
As we can see this method just marks an element as dirty and this element should be rebuilt on next frame. Rebuild means call method build on the coresponding widget element.
But what about "Whole sub-tree rebuilds when I rebuild InheritedWidget?". Here we should remember that Widgets are immutable and if you create new widget Flutter will rebuild the sub-tree. How can we fix it?
- Cache widgets by hands(manually)
- Use const because const create the only one instance of value/class
From the docs:
[BuildContext.inheritFromWidgetOfExactType] obtains the nearest widget of the given type, which must be the type of a concrete InheritedWidget subclass, and registers this build context with that widget such that when that widget changes (or a new widget of that type is introduced, or the widget goes away), this build context is rebuilt so that it can obtain new values from that widget.
This is typically called implicitly from of() static methods, e.g. Theme.of.
As the OP noted, an InheritedWidget
instance does not change... but it can be replaced with a new instance at the same location in the widget tree. When that happens it is possible that the registered widgets need to be rebuilt. The InheritedWidget.updateShouldNotify
method makes this determination. (See: docs)
그렇다면 인스턴스를 어떻게 교체 할 수 있습니까? InheritedWidget
인스턴스는에 포함 할 수있다 StatefulWidget
새로운 인스턴스와 오래된 인스턴스를 대체 할 수있다.
참고 URL : https://stackoverflow.com/questions/49491860/flutter-how-to-correctly-use-an-inherited-widget
'programing tip' 카테고리의 다른 글
자신의 Android 블루투스 트래픽 스니핑 / 로깅 (0) | 2020.11.12 |
---|---|
ON CONFLICT 절에서 여러 충돌 대상 사용 (0) | 2020.11.12 |
웹 페이지를 이미지로 변환 (0) | 2020.11.12 |
Vinay Deolalikar가 P! = NP라는 증거를 설명하십시오. (0) | 2020.11.12 |
mysql과 mysql2 gem의 차이점 (0) | 2020.11.12 |