Flutter : 상속 된 위젯을 올바르게 사용하는 방법?


103

InheritedWidget을 사용하는 올바른 방법은 무엇입니까? 지금까지 위젯 트리 아래로 데이터를 전파 할 수있는 기회를 제공한다는 것을 이해했습니다. 극단적으로 RootWidget으로 넣으면 모든 경로의 트리에있는 모든 위젯에서 액세스 할 수 있습니다. 어떻게 든 전역 또는 싱글 톤에 의존하지 않고 내 위젯에 대해 ViewModel / Model에 액세스 할 수 있도록해야하기 때문에 괜찮습니다.

그러나 InheritedWidget은 불변이므로 어떻게 업데이트 할 수 있습니까? 그리고 더 중요한 것은 내 Stateful 위젯이 하위 트리를 재 구축하기 위해 트리거되는 방법은 무엇입니까?

불행히도 문서는 여기에 매우 불분명하며 많은 토론 후 아무도 그것을 사용하는 올바른 방법을 실제로 알지 못하는 것 같습니다.

Brian Egan의 인용문을 추가합니다.

예, 데이터를 트리 아래로 전파하는 방법이라고 생각합니다. API 문서에서 헷갈리는 점 :

"상속 된 위젯은 이러한 방식으로 참조 될 때 상속 된 위젯 자체가 상태를 변경할 때 소비자가 다시 빌드하게합니다."

이 글을 처음 읽었을 때 다음과 같이 생각했습니다.

InheritedWidget에 일부 데이터를 채우고 나중에 변경할 수 있습니다. 해당 변형이 발생하면 InheritedWidget을 참조하는 모든 위젯을 다시 빌드합니다.

InheritedWidget의 상태를 변경하려면 StatefulWidget으로 래핑해야합니다. 그런 다음 실제로 StatefulWidget의 상태를 변경하고이 데이터를 InheritedWidget에 전달하면 데이터가 모든 자식에게 전달됩니다. 그러나이 경우 InheritedWidget을 참조하는 위젯뿐만 아니라 StatefulWidget 아래 전체 트리를 다시 빌드하는 것처럼 보입니다. 그 맞습니까? 아니면 updateShouldNotify가 false를 반환하는 경우 InheritedWidget을 참조하는 위젯을 건너 뛰는 방법을 알고 있습니까?

답변:


108

잘못된 견적에서 문제가 발생합니다.

말했듯이 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내부를 감싸십시오 !StatefulWidgetInheritedWidget

실제 코드의 최종 결과는 다음과 같습니다.

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);

  static MyInheritedData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedData>();
  }

  @override
  bool updateShouldNotify(MyInheritedData oldWidget) {
    return oldWidget.myField != myField ||
        oldWidget.onMyFieldChange != onMyFieldChange;
  }
}

그러나 새 InheritedWidget을 만들면 전체 트리가 다시 작성되지 않습니까?

아니요, 반드시 그런 것은 아닙니다. 새로운 InheritedWidget은 잠재적으로 이전과 똑같은 자식을 가질 수 있습니다. 정확히 말하면 같은 경우를 의미합니다. 이전과 동일한 인스턴스를 가진 위젯은 다시 빌드하지 않습니다.

그리고 대부분의 상황에서 (앱의 루트에 inheritedWidget이 있음) 상속 된 위젯은 constant 입니다. 따라서 불필요한 재 구축이 없습니다.


1
그러나 새 InheritedWidget을 만들면 전체 트리가 다시 빌드되지 않을까요? 그렇다면 청취자가 필요한 이유는 무엇입니까?
토마스

1
첫 번째 의견에 대해 제 답변에 세 번째 부분을 추가했습니다. 지루한 것에 관해서 : 동의하지 않습니다. 코드 스 니펫은이를 상당히 쉽게 생성 할 수 있습니다. 데이터에 액세스하는 것은을 호출하는 것만 큼 간단합니다 MyInherited.of(context).
Rémi Rousselet

3
아니 확신 관심이 당신에게 경우에, 그러나이 기술로 업데이트 샘플 가지고 : github.com/brianegan/flutter_architecture_samples/tree/master/... A는 확실히 지금은 덜 중복 비트! 해당 구현에 대한 다른 제안이 있다면 잠시 시간을내어 코드 검토를 좋아할 것입니다. :) 여전히이 논리 교차 플랫폼 (Flutter 및 웹)을 공유하고 테스트 가능한지 확인하는 가장 좋은 방법을 찾으려고 노력 중입니다 ( 특히 비동기 물건).
brianegan

4
때문에 updateShouldNotify테스트가 항상 같은 언급되는 MyInheritedState경우, 항상 반환하지 않습니다 false? 확실히의 build방법은 MyInheritedState_MyInherited인스턴스를 만드는 것이지만 data필드는 항상 this아니오를 참조 합니까? 문제가 있습니다 true. 하드 코드 만 있으면 작동합니다 .
cdock

2
@cdock 그래 내 나쁜. 분명히 작동하지 않을 것이기 때문에 내가 왜 그랬는지 기억하지 마십시오. 참으로 편집하여 수정했습니다. 감사합니다.
Rémi Rousselet

20

TL; DR

updateShouldNotify 메서드 내에서 무거운 계산을 사용하지 말고 위젯을 만들 때 new 대신 const 를 사용하십시오.


우선, 우리는 Widget, Element, Render 객체가 무엇인지 이해해야합니다.

  1. 렌더 객체는 실제로 화면에 렌더링되는 것입니다. 그들은 가변적 이며 페인팅 및 레이아웃 로직을 포함합니다. 렌더 트리는 웹의 DOM (Document Object Model)과 매우 유사하며이 트리에서 렌더 객체를 DOM 노드로 볼 수 있습니다.
  2. 위젯 -렌더링해야하는 항목에 대한 설명입니다. 그들은 불변 하고 저렴합니다. 따라서 위젯이 "무엇입니까?"(선언적 접근 방식) 질문에 대답하면 Render 객체는 "어떻게?"(명령 ​​적 접근 방식) 질문에 대답합니다. 웹의 비유는 "가상 DOM"입니다.
  3. Element / BuildContext- 위젯렌더 객체 간의 프록시 입니다. 여기에는 트리 *에서 위젯의 위치에 대한 정보와 해당 위젯이 변경 될 때 Render 객체를 업데이트하는 방법이 포함되어 있습니다.

이제 InheritedWidget 및 BuildContext의 메서드 inheritFromWidgetOfExactType을 살펴볼 준비가되었습니다 .

예를 들어, InheritedWidget에 대한 Flutter의 문서에서이 예제를 고려하는 것이 좋습니다.

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-우리의 경우 하나의 중요한 메소드 인 updateShouldNotify 를 구현하는 위젯입니다 . updateShouldNotify- 하나의 매개 변수 oldWidget 을 받아들이고 부울 값 (true 또는 false)을 반환 하는 함수입니다 .

다른 위젯과 마찬가지로 InheritedWidget 에는 해당하는 Element 객체가 있습니다. 그것은이다 InheritedElement . InheritedElement 는 새로운 위젯을 만들 때마다 위젯에서 updateShouldNotify 를 호출 합니다 (조상에서 setState 를 호출 합니다). updateShouldNotifytrue를 반환 InheritedElement 는 종속성 (?)을 반복 하고 didChangeDependencies 를 호출 합니다.

InheritedElement가 가져 오는 위치 종속성을 습니까? 여기에서 inheritFromWidgetOfExactType 메소드를 살펴 보겠습니다 .

inheritFromWidgetOfExactType -BuildContext 및 모든 요소에 정의 된이 메소드 는 BuildContext 인터페이스를 구현합니다 (Element == BuildContext). 따라서 모든 요소에는이 방법이 있습니다.

inheritFromWidgetOfExactType의 코드를 살펴 보겠습니다.

final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
  assert(ancestor is InheritedElement);
  return inheritFromElement(ancestor, aspect: aspect);
}

여기서 우리는 _inheritedWidgets 에서 조상을 찾으려고합니다. 매핑 된 합니다. 조상을 찾으면 inheritFromElement 를 호출 합니다 .

inheritFromElement 코드 :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. 현재 요소 (_dependencies.add (ancestor))의 종속성으로 ancestor를 추가합니다.
  2. 현재 요소를 조상의 종속성에 추가합니다 (ancestor.updateDependencies (this, aspect)).
  3. inheritFromWidgetOfExactType의 결과로 조상의 위젯을 반환합니다 (ancestor.widget 반환).

이제 InheritedElement가 종속성을 얻는 위치를 알았습니다.

이제 didChangeDependencies 메소드를 살펴 보겠습니다 . 모든 요소에는 다음 방법이 있습니다.

  void didChangeDependencies() {
    assert(_active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

우리가 볼 수 있듯이이 메서드는 요소를 더티 로 표시 하고이 요소는 다음 프레임에서 다시 작성되어야합니다. Rebuild 는 코어에 해당하는 위젯 요소에서 메서드 빌드 를 호출하는 것을 의미합니다 .

그러나 "InheritedWidget을 다시 빌드 할 때 전체 하위 트리가 다시 빌드됩니까?"는 어떻습니까? 여기서 우리는 위젯은 불변이며 새 위젯을 생성하면 Flutter가 하위 트리를 다시 빌드한다는 것을 기억해야합니다. 어떻게 고칠 수 있습니까?

  1. 손으로 위젯 캐시 (수동)
  2. 사용 CONST CONST이 때문에 유일하게 하나 개의 인스턴스 생성 값 / 클래스를

1
훌륭한 설명 maksimr. 가장 혼란스러운 점은 inheritedWidget이 교체되었을 때 전체 하위 트리가 어쨌든 다시 빌드되면 updateShouldNotify ()의 요점은 무엇입니까?
Panda World

3

로부터 문서 :

[BuildContext.inheritFromWidgetOfExactType]은 구체적인 InheritedWidget 서브 클래스의 유형이어야하는 주어진 유형의 가장 가까운 위젯을 가져 와서 해당 위젯이 변경 될 때 (또는 해당 유형의 새 위젯이 도입되면, 또는 위젯이 사라짐)이 빌드 컨텍스트는 해당 위젯에서 새 값을 얻을 수 있도록 다시 빌드됩니다.

이것은 일반적으로 Theme.of와 같은 of () 정적 메서드에서 암시 적으로 호출됩니다.

OP에서 언급했듯이 InheritedWidget인스턴스는 변경되지 않지만 위젯 트리의 동일한 위치에서 새 인스턴스로 대체 될 수 있습니다. 이 경우 등록 된 위젯을 다시 빌드해야 할 수 있습니다. 이 InheritedWidget.updateShouldNotify방법은이 결정을 내립니다. (참조 : 문서 )

그렇다면 인스턴스를 어떻게 교체 할 수 있습니까? InheritedWidget인스턴스는에 포함 할 수있다 StatefulWidget새로운 인스턴스와 오래된 인스턴스를 대체 할 수있다.


-1

InheritedWidget은 앱의 중앙 집중식 데이터를 관리하고 자식에게 전달합니다. 여기에 설명 된대로 여기에 카트 수를 저장할 수 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.