5/5 (4) Разъяснение понятий Widget, State, Context, InheritedWidget

В этой статье рассматриваются важные понятия Widget, State, Context и InheritedWidget в приложениях Flutter. Особое внимание уделяется InheritedWidget, который является одним из наиболее важных и менее документированных виджетов.

Предисловие

Понятия Widget, State и Context во Flutter являются одними из самых важных понятий, которые каждый разработчик Flutter должен полностью понимать.

Однако документация огромна, и эта концепция не всегда четко объяснена.

Я объясню эти понятия своими словами и ярлыками, зная, что это может привести к шоку некоторых пуристов, но настоящая цель этой статьи — попытаться прояснить следующие темы:

  • Разница между виджетами Stateful и Stateless
  • Что такое Context
  • Что такое State и как его использовать
  • Связь между контекстом и его объектом состояния
  • InheritedWidget и способ распространения информации внутри дерева виджетов
  • Понятие перепостроения

Часть 1. Концепция

Понятие виджета

Во Flutter все является виджетом.

[info] Думайте о виджете как о визуальном компоненте (или компоненте, который взаимодействует с визуальным аспектом приложения). [/info]

Когда вам нужно создать что-то, что прямо или косвенно связано с макетом, вы используете виджеты.

Понятие дерева виджетов

Виджеты организованы в виде древовидной структуры.

Виджет, который содержит другие виджеты, называется родительским виджетом parent Widget (или контейнером виджетов). Виджеты, которые содержатся в родительском виджете, называются дочерними виджетами children Widgets.

Давайте проиллюстрируем это с помощью базового приложения, которое автоматически генерируется Flutter. Вот упрощенный код, ограниченный методом сборки build:

@override
Widget build(BuildContext){
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
}

Если мы теперь рассмотрим этот базовый пример, мы получим следующую древовидную структуру виджетов (ограничив список виджетов, присутствующих в коде):

Понятие контекста

Еще одним важным понятием является контекст Context виджета.

Контекст — это не что иное, как ссылка на расположение виджета в древовидной структуре всех созданных виджетов.

[info] Короче говоря, думайте о контексте как о части дерева виджетов, где виджет прикреплен к этому дереву. [/info]

Контекст принадлежит только одному виджету.

Если виджет «А» имеет дочерние виджеты, контекст виджета «А» станет родительским контекстом непосредственно дочернего контекста.

Читая это, становится ясно, что контексты связаны между собой и составляют дерево контекстов отношением parent-children.

Если мы сейчас попытаемся проиллюстрировать понятие контекста Context на предыдущей диаграмме, мы получим такое представление(все еще в очень упрощенном виде), где каждый цвет представляет контекст (кроме MyApp, который отличается):

[info] Видимость контекста (упрощенное утверждение). Виджет виден только в его собственном контексте или в контексте его родительского(их) контекста (ов). [/info]

Из этого утверждения мы можем извлечь, что из дочернего контекста легко найти виджет-предок (parent).

[info] Примером является рассмотрение Scaffold > Center > Column > Text: context.ancestorWidgetOfExactType (Scaffold) => возвращает первый Scaffold путем перехода к древовидной структуре из контекста Text. [/info]

Из родительского контекста также можно найти дочерний (child) виджет, но делать это не рекомендуется (мы обсудим это позже).

Типы виджетов

Виджеты бывают двух типов:

  • Stateless Widget
  • Statefull Widget

Stateless Widget

Некоторые из этих визуальных компонентов не зависят ни от чего, кроме их собственной информации о конфигурации, которая предоставляется во время сборки ее непосредственным родителем.

[info] Другими словами, эти виджеты не должны заботиться о каких-либо изменениях после их создания. [/info]

Эти виджеты называются виджетами без состояния Stateless Widget.

Типичными примерами таких виджетов могут быть «Text», «Row», «Column», «Container»… где во время сборки мы просто передаем им некоторые параметры.

Параметры могут быть чем-либо из декораций, размеров или даже других виджетов. Это не имеет значения. Единственное, что важно, это то, что эта конфигурация после применения не изменится до следующего процесса сборки.

[info] Виджет без состояния может быть нарисован только один раз при загрузке / сборке виджета, что означает, что этот виджет нельзя перерисовать на основе каких-либо событий или действий пользователя. [/info]

Жизненный цикл виджета без сохранения состояния Stateless Widget

Вот типичная структура кода, связанного с виджетом без сохранения состояния Stateless Widget.

Как вы можете видеть, мы можем передать некоторые дополнительные параметры его конструктору. Однако имейте в виду, что эти параметры НЕ будут изменяться на более позднем этапе и должны использоваться только как есть.

class MyStatelessWidget extends StatelessWidget {

	MyStatelessWidget({
		Key key,
		this.parameter,
	}): super(key:key);

	final parameter;

	@override
	Widget build(BuildContext context){
		return new ...
	}
}

Даже если есть другой метод, который может быть переопределен (createElement), последний едва ли будет переопределен. Единственное, что нужно переопределить — это build.

Stateful Widget

Некоторые другие виджеты будут обрабатывать некоторые внутренние данные, которые будут меняться в течение срока службы виджета. Эти данные, следовательно, становятся динамическими.

Набор данных, который хранится в этом виджете и который может изменяться в течение срока службы этого виджета, называется состоянием State.

Эти виджеты называются Stateful Widgets.

Примером такого виджета может быть список флажков, которые пользователь может выбрать, или кнопка, которая отключена в зависимости от условия.

Понятие State

State определяет «поведенческую» часть экземпляра StatefulWidget.

Он содержит информацию, направленную на взаимодействие/вмешательство interacting / interferring в виджете в терминах:

  • поведение
  • макет

[info] Любые изменения, которые применяются к State, вынуждают Виджет перестраиваться(rebuild). [/info]

Взаимосвязь между State и Context

Для Stateful widgets состояние связано с контекстом Context. Эта ассоциация является постоянной permanent, и объект State никогда не изменит своего контекста.

Даже если контекст виджета можно перемещать по древовидной структуре, состояние State будет оставаться связанным с этим контекстом Context.

Когда состояние State связано с контекстом Context, оно считается подключенным(mounted).

[info] ГИПЕР ВАЖНО: Поскольку объект State связан с контекстом, это означает, что объект State НЕ (непосредственно) доступен через другой контекст! (мы еще обсудим это через несколько минут). [/info]

Stateful Widget жизненный цикл

Теперь, когда основные понятия были введены, пришло время погрузиться немного глубже …

Вот типичная структура кода, связанного с виджетом с отслеживанием состояния Stateful Widget.

Поскольку основная цель этой статьи состоит в том, чтобы объяснить понятие State в терминах «переменных» данных, я намеренно пропущу любое объяснение, касающееся некоторых переопределяемых( overridable ) методов Stateful Widget, которые конкретно не связаны с этим. Эти переопределяемые методы: didUpdateWidget, deactivate, reassemble . Это будет обсуждаться в следующей статье

class MyStatefulWidget extends StatefulWidget {

	MyStatefulWidget({
		Key key,
		this.parameter,
	}): super(key: key);
	
	final parameter;
	
	@override
	_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State {

	@override
	void initState(){
		super.initState();
		
		// Additional initialization of the State
	}
	
	@override
	void didChangeDependencies(){
		super.didChangeDependencies();
		
		// Additional code
	}
	
	@override
	void dispose(){
		// Additional disposal code
		
		super.dispose();
	}
	
	@override
	Widget build(BuildContext context){
		return new ...
	}
}

На следующей диаграмме показана (упрощенная версия) последовательность действий / вызовов, связанных с созданием виджета с отслеживанием состояния. В правой части диаграммы вы заметите внутренний статус объекта State во время потока. Вы также увидите момент, когда контекст связан с состоянием и, таким образом, станет доступным (mounted ).

Итак, давайте объясним это с некоторыми дополнительными деталями:

Метод initState()

Метод initState() — это самый первый метод (после конструктора), который вызывается после создания объекта State. Этот метод должен быть переопределен, когда вам нужно выполнить дополнительные инициализации. Типичные инициализации связаны с анимацией, контроллерами … Если вы переопределяете этот метод, вам нужно вызвать метод super.initState() и обычно сначала.

В этом методе контекст context доступен, но вы по-настоящему не можете его использовать, так как фреймворк еще не полностью связал с ним состояние.

Когда метод initState() завершен, объект State теперь инициализируется и контекст становится доступным.

Когда метод initState() завершен, объект State теперь инициализируется и контекст становится доступным.

Этот метод больше не будет вызываться во время жизни этого объекта State.

Метод didChangeDependencies()

Метод didChangeDependencies() — это второй вызываемый метод.

На данном этапе, когда контекст context доступен, вы можете использовать его.

Этот метод обычно переопределяется, если ваш виджет связан с InheritedWidget и / или если вам нужно инициализировать некоторых слушателей listeners (в зависимости от контекста context).

Обратите внимание, что если ваш виджет связан с InheritedWidget, этот метод будет вызываться каждый раз, когда этот виджет будет перестроен.

Если вы переопределите этот метод, вы должны сначала вызвать super.didChangeDependencies().

Метод build()

Метод build(BuildContext context) вызывается после didChangeDependencies()didUpdateWidget).

Это место, где вы строите свой виджет (и, возможно, любое поддерево).

Этот метод будет вызываться каждый раз, когда изменяется ваш объект State (или когда InheritedWidget должен уведомить «зарегистрированные» виджеты) !!

Для принудительного перестроения вы можете вызвать метод setState (() {…}).

Метод dispose()

Метод dispose() вызывается, когда виджет отбрасывается.

Переопределите этот метод, если вам нужно выполнить некоторую очистку (например, прослушиватели), а затем вызвать super.dispose() сразу после.

Stateless или Stateful Widget?

Это вопрос, который многие разработчики должны задать себе: нужен ли мой виджет без сохранения состояния( Stateless ) или с сохранением состояния( Stateful )?

Чтобы ответить на этот вопрос, задайте себе вопрос:

[info] Нужно ли учитывать переменную, которая меняется во время жизни моего виджета и, если он будет изменен, заставит виджет перестраиваться. [/info]

Если ответ на вопрос «да», то вам нужен виджет с сохранением состояния Stateful , в противном случае вам нужен виджет без сохранения состояния Stateless .

Некоторые примеры:

  • виджет для отображения списка флажков. Для отображения флажков необходимо учитывать массив элементов. Каждый элемент — это объект с заголовком и статусом. Если вы установите флажок, соответствующий элемент item.status будет переключен;

В этом случае вам нужно использовать виджет Stateful, чтобы запомнить состояние элементов, чтобы иметь возможность перерисовать флажки.

  • экран с формой. Экран позволяет пользователю заполнять виджеты формы и отправлять форму на сервер.

В этом случае, если вам не требуется проверять форму или выполнять какие-либо другие действия перед ее отправкой, может быть достаточно виджета без сохранения состоянияStateless .

Stateful Widget состоит из 2 частей

Помните структуру виджета с состоянием Stateful ? Есть 2 части:

1. Основное определение виджета
class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({
		Key key,
		this.color,
	}): super(key: key);
	
	final Color color;

	@override
	_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

Первая часть «MyStatefulWidget» обычно является публичной( public ) частью виджета. Вы создаете экземпляр этой части, когда хотите добавить ее в дерево виджетов. Эта час��ь не изменяется во время жизни виджета, но может принимать параметры, которые могут использоваться его соответствующим экземпляром состояния State.

[info] Обратите внимание, что любая переменная, определенная на уровне этой первой части виджета, обычно НЕ будет меняться в течение своего срока с��ужбы. [/info]

2. Определение состояния виджета
class _MyStatefulWidgetState extends State {
    ...
	@override
	Widget build(BuildContext context){
	    ...
	}
}

Вторая часть «MyStatefulWidgetState» — это часть, которая изменяется в течение срока службы виджета и вынуждает этот конкретный экземпляр виджета перестраиваться при каждом применении модификации. Символ «_» в начале имени делает класс закрытым для файла .dart.

Если вам нужно сделать ссылку на этот класс вне файла .dart, не используйте префикс «_» .

Класс _MyStatefulWidgetState может обращаться к любой переменной, которая хранится в MyStatefulWidget, используя widget. {Имя переменной}. В этом примере: widget.color .


Уникальная идентификация виджета

В Flutter каждый виджет уникален. Этот уникальный идентификатор определяется платформой во время сборки / рендеринга.

Этот уникальный идентификатор соответствует необязательному параметру Key. Если опущено, Flutter сгенерирует один для вас.

В некоторых случаях может потребоваться принудительное использование этого ключа, чтобы вы могли получить доступ к виджету по его ключу.

Для этого вы можете использовать один из следующих помощников: GlobalKey, LocalKey, UniqueKey или ObjectKey.

GlobalKey гарантирует, что ключ уникален во всем приложении.

Чтобы создать уникальную идентификацию виджета:

    GlobalKey myKey = new GlobalKey();
    ...
    @override
    Widget build(BuildContext context){
        return new MyWidget(
            key: myKey
        );
    }

Часть 2. Как получить доступ к объекту State во Flutter?

Как было объяснено ранее, состояние State связано с одним контекстом, а контекст связан с экземпляром виджета.

1. Сам виджет

Теоретически, единственным, кто может получить доступ к состоянию State , является сам State-виджет.

В этом случае нет никаких трудностей. Класс State виджета обращается к любой из своих переменных.

2. Непосредственный дочерний виджет

Иногда родительскому виджету может потребоваться получить доступ к состоянию одного из его прямых потомков для выполнения определенных задач.

В этом случае, чтобы получить доступ к этим прямым дочерним состояниям State, вам необходимо их знать.

Самый простой способ вызвать кого-то или чего-то — это через имя. Во Flutter каждый виджет имеет уникальную идентичность, которая определяется во время сборки / рендеринга платформой. Как было показано ранее, вы можете принудительно идентифицировать виджет, используя параметр ключа key .

    ...
    GlobalKey myWidgetStateKey = new GlobalKey();
    ...
    @override
    Widget build(BuildContext context){
        return new MyStatefulWidget(
            key: myWidgetStateKey,
            color: Colors.blue,
        );
    }

После идентификации родительский parent виджет может получить доступ к состоянию State своего потомка через myWidgetStateKey.currentState .

Давайте рассмотрим базовый пример, который показывает SnackBar, когда пользователь нажимает кнопку. Поскольку SnackBar является дочерним виджетом Scaffold , он не доступен напрямую любому другому потомку тела Scaffold (помните понятие контекста и его иерархию / древовидную структуру?). Следовательно, единственный способ получить к нему доступ — через ScaffoldState, который предоставляет открытый метод для отображения SnackBar.

    class _MyScreenState extends State {
        /// the unique identity of the Scaffold
        final GlobalKey _scaffoldKey = new GlobalKey();

        @override
        Widget build(BuildContext context){
            return new Scaffold(
                key: _scaffoldKey,
                appBar: new AppBar(
                    title: new Text('My Screen'),
                ),
                body: new Center(
                    new RaiseButton(
                        child: new Text('Hit me'),
                        onPressed: (){
                            _scaffoldKey.currentState.showSnackBar(
                                new SnackBar(
                                    content: new Text('This is the Snackbar...'),
                                )
                            );
                        }
                    ),
                ),
            );
        }
    }

3. Предок Виджет

Предположим, что у вас есть виджет, который принадлежит поддереву другого виджета, как показано на следующей диаграмме

Для этого необходимо выполнить 3 условия:

1. «Виджет со State» (в красном) должен раскрыть его состояние State

Чтобы раскрыть свое состояние State, виджет должен записать его во время создания следующим образом:

class MyExposingWidget extends StatefulWidget {

   MyExposingWidgetState myState;
	
   @override
   MyExposingWidgetState createState(){
      myState = new MyExposingWidgetState();
      return myState;
   }
}
2. «Состояние State виджета» должно выставлять некоторые методы getters/setters

Чтобы позволить «незнакомцу» установить / получить (get/set) свойство State, состоянию виджета State необходимо разрешить доступ посредством:

  • public property (not recommended)
  • getter / setter

Пример:

class MyExposingWidgetState extends State{
   Color _color;
	
   Color get color => _color;
   ...
}
3. «Виджет, заинтересованный в получении состояния State» (синим цветом) должен получить ссылку на состояние State
class MyChildWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context){
      final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
      final MyExposingWidgetState state = widget?.myState;
		
      return new Container(
         color: state == null ? Colors.blue : state.color,
      );
   }
}

Это решение легко реализовать, но как дочерний виджет узнает, когда его нужно перестроить?

С этим решением это не так. Придется подождать, пока произойдет перестроение, чтобы обновить его содержимое, что не очень удобно.

В следующем разделе рассматривается понятие Inherited Widget, которое дает решение этой проблемы.


InheritedWidget

Вкратце и простыми словами, InheritedWidget позволяет эффективно распространять информацию по дереву виджетов.

InheritedWidget — это специальный виджет, который вы помещаете в дерево виджетов как родительский элемент другого поддерева. Все виджеты, входящие в это поддерево, будут иметь возможность взаимодействовать с данными, предоставляемыми этим InheritedWidget.

Основы

Чтобы объяснить это, давайте рассмотрим следующий фрагмент кода:

class MyInheritedWidget extends InheritedWidget {
   MyInheritedWidget({
      Key key,
      @required Widget child,
      this.data,
   }): super(key: key, child: child);
	
   final data;
	
   static MyInheritedWidget of(BuildContext context) {
      return context.inheritFromWidgetOfExactType(MyInheritedWidget);
   }

   @override
   bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}

Этот код определяет виджет с именем «MyInheritedWidget», предназначенный для «совместного использования» некоторых данных между всеми виджетами, являющимися частью дочернего поддерева.

Как упоминалось ранее, InheritedWidget должен располагаться в верхней части дерева виджетов, чтобы иметь возможность распространять / делиться некоторыми данными, это объясняет дочерний элемент @required Widget, который передается базовому конструктору InheritedWidget.

Статический метод static MyInheritedWidget of(BuildContext context) позволяет всем дочерним виджетам получить экземпляр ближайшего MyInheritedWidget, который окружает контекст (см. Далее).

Наконец, переопределенный метод updateShouldNotify используется, чтобы сообщить InheritedWidget, нужно ли передавать уведомления всем дочерним виджетам (которые зарегистрированы / подписаны), если к данным будет применена модификация (см. Позже).

Поэтому нам нужно поместить его на уровне узла дерева следующим образом:

class MyParentWidget... {
   ...
   @override
   Widget build(BuildContext context){
      return new MyInheritedWidget(
         data: counter,
         child: new Row(
            children: [
               ...
            ],
         ),
      );
   }
}

Как дочерний элемент получает доступ к данным InheritedWidget?

Во время создания дочернего элемента последний получит ссылку на InheritedWidget следующим образом:

class MyChildWidget... {
   ...
	
   @override
   Widget build(BuildContext context){
      final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
		
      ///
      /// From this moment, the widget can use the data, exposed by the MyInheritedWidget
      /// by calling:  inheritedWidget.data
      ///
      return new Container(
         color: inheritedWidget.data.color,
      );
   }
}

Как сделать взаимодействие между виджетами?

Рассмотрим следующую диаграмму, которая показывает древовидную структуру виджетов

Чтобы проиллюстрировать тип взаимодействия, предположим следующее:

  • «Виджет А» — это кнопка, которая добавляет товар в корзину;
  • «Виджет B» — это текст, отображающий количество товаров в корзине;
  • «Виджет C» находится рядом с виджетом B и представляет собой текст с любым текстом внутри;
  • Мы хотим, чтобы «Виджет B» автоматически отображал правильное количество товаров в корзине, как только нажимается «Виджет А», но мы не хотим, чтобы «Виджет С» восстанавливался.

InheritedWidget — это именно тот виджет, который можно использовать для этого!

Пример по коду

Давайте сначала напишем код, и объяснения будут следовать:

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State{
  /// List of Items
  List _items = [];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: [
            new WidgetA(),
            new Container(
              child: new Row(
                children: [
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}

Пояснения

В этом очень простом примере:

  • _MyInherited — это InheritedWidget, который воссоздается каждый раз, когда мы добавляем элемент посредством нажатия на кнопку «Виджет A».
  • MyInheritedWidget — это виджет с состоянием, который содержит список элементов. Это состояние доступно через  static MyInheritedWidgetState.of(BuildContext context)
  • MyInheritedWidgetState предоставляет один метод getter ( itemsCount ) и один метод ( addItem ), чтобы они могли использоваться виджетами, являющимися частью дерева дочерних виджетов.
  • Каждый раз, когда мы добавляем элемент в состояние, MyInheritedWidgetState перестраивает
  • Класс MyTree просто создает дерево виджетов, имея MyInheritedWidget вкачестве родителя дерева
  • WidgetA — это простой RaisedButton, который при нажатии вызывает метод addItem из ближайшего MyInheritedWidget
  • WidgetB — это простой текст, который отображает количество элементов, представленных на уровне ближайшего MyInheritedWidget

Как все это работает?

Регистрация виджета для последующих уведомлений

Когда дочерний виджет вызывает MyInheritedWidget.of ( context ) , он вызывает следующий метод MyInheritedWidget, передавая свой собственный контекст .

 
static MyInheritedWidgetState of(BuildContext context) {
	return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }

Внутренне, поверх простого возврата экземпляра MyInheritedWidgetState, он также подписывает потребительский( consumer ) виджет на уведомления об изменениях.

За кулисами простой вызов этого статического метода фактически делает 2 вещи:

  • потребитель ( consumer )  виджет автоматически добавляются в список абонентов , которые будут перестроены , когда модификация применяется к InheritedWidget (здесь _MyInherited )
  • что данные , указанные в _MyInherited виджета (ака MyInheritedWidgetState ) возвращается к потребителю

Поток

Поскольку и «Виджет А», и «Виджет Б» подписаны на InheritedWidget, так что, если модификация применяется к _MyInherited , поток операций будет следующим (упрощенная версия) при нажатии RaisedButton для Виджета А:

  1. Вызов производится с AddItem методом MyInheritedWidgetState
  2. Метод MyInheritedWidgetState.addItem добавляет новый элемент в список
  3. setState () вызывается для перестройки MyInheritedWidget
  4. Создается новый экземпляр _MyInherited с новым содержимым списка.
  5. _MyInherited записывает новое состояние, которое передается в аргументе ( данных )
  6. Как InheritedWidget , он проверяет , есть ли необходимость уведомить о потребителях (ответ верно)
  7. Он перебирает весь список потребителей (здесь виджеты A и W) и просит их перестроить
  8. Поскольку Wiget C не является потребителем , он не перестраивается.

Так что это работает!

Однако оба виджета A и W виджета B перестраиваются, а перестраивать Wiget A бесполезно, поскольку для него ничего не изменилось. Как предотвратить это?

Не позволяйте некоторым виджетам перестраиваться при доступе к унаследованному виджету

Причина, по которой Widget A также был перестроен, связана с тем, как он обращается к MyInheritedWidgetState .

Как мы видели ранее, факт вызова метода context.inheritFromWidgetOfExactType() автоматически подписывает виджет на список потребителей .

Решение для предотвращения этой автоматической подписки при одновременном предоставлении Widget A доступа к MyInheritedWidgetState заключается в изменении статического метода MyInheritedWidget следующим образом:

  
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
                    : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }

Добавляя логический дополнительный параметр…

  • Если параметр rebuild равен true (по умолчанию), мы используем обычный подход (и виджет будет добавлен в список подписчиков)
  • Если восстановить параметр является ложным, мы до сих пор получить доступ к данным , но без использования внутренней реализации в InheritedWidget

Итак, чтобы завершить решение, нам также нужно немного обновить код виджета A следующим образом (мы добавили ложный дополнительный параметр):

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context, false);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

Вот и все, виджет А больше не перестраивается, когда мы нажимаем на него.

Специальное примечание для маршрутов, диалогов …

[info] Маршруты, контексты диалогов привязаны к приложению. Это означает, что даже если внутри экрана A вы запрашиваете отображение другого экрана B (например, поверх текущего), на любом из двух экранов нет простого способа связать их собственный контекст. Единственный способ узнать на экране B что-либо о контексте экрана A — это получить его с экрана A в качестве параметра Navigator.of (context) .push (….) [/info]

Выводы

По этим темам еще так много можно сказать … особенно по InheritedWidget .

В следующей статье я представлю понятие уведомителями / радиослушателей, который также очень интересно использовать в контексте государства и пути передачи данных.

Итак, следите за обновлениями и счастливого кодирования.

Официальный источник данной статьи https://www.didierboelens.com/2018/06/widget—state—context—inheritedwidget/

Пожалуйста, оцените материал

WebSofter

Web - технологии