Егор Долгов
Егор Долгов
За все время: 6922 просмотра, 3597 посетителей.
За последние 30 дней: 102 просмотра, 54 посетителя.

Context - как это работает во Flutter

Перевод статьи Flutter in Context автора Greg Perry

Детальный разбор класса BuidContext
(Эта статья является частью серии Decode Flutter Series)

Вы уже знакомы с контекстными объектами? Я имею в виду объекты класса BuildContext с именем context, который постоянно передается функции build(), а также является необходимым параметром для множества статических функций:

Они помогают идти вверх или сквозь «дерево рендеринга» (или же «дерево виджетов»). Мы подробно рассмотрим эти объекты в этой статье, заглянем «под капот» фреймворка Flutter и выясним, из чего именно состоит этот объект BuiltContext под названием context. А это означает, что мы «пойдем» сквозь код. Собственно, не буду долго тянуть и держать вас в неведении и прямо сейчас рассажу вам, что именно это за объект: это элемент.

Нажмите на скриншот, чтобы открыть отрывок кода.

Я всегда предпочитаю использовать скриншоты в своих статьях для отображения самого концепта, а не просто кода. К тому же, я считаю, что с ними проще работать. Однако, вы можете кликнуть на изображение, чтобы открыть сам код в gist или в Github. Также эту статью лучше читать не на мобильном устройстве, а через компьютер.

А теперь начнем!

Not In Context

Так уж получилось, что мы не собираемся подробно рассматривать сам класс BuildContext. Хоть он и является ключевым во фреймворке Flutter, просто представления о том, что он делает в приложении, будет вполне достаточно. К тому же, это абстрактный класс – вы должны создать подкласс и реализовать все поля и функции, что он содержит.
Ниже предоставлен скриншот класс со всей его документацией и удаленными устаревшими функциями – просто чтобы дать вам представление об его роли во Flutter. Вы даже можете узнать некоторые функции и даже удивиться тому, в каком классе они находятся. Затем мы определим точный подкласс, который использует BuildContext. Правда, эту загадку я уже раскрыл выше.

Элементы виджетов

Отойдем немного назад и посмотрим сперва на класс StatelessWidget. Ниже расположен скриншот одного такого класса со всей его документацией, чтобы вы могли рассмотреть, из чего он состоит. Не очень-то много всего, правда? Это абстрактный класс, и его подкласс, конечно, должен реализовывать метод build() – это мы уже знаем. Однако, что насчет метода createElement()? Он создает другой класс под названием StatelessElement и,фактически, ссылается на себя в качестве параметра.

Я решил оставить всю немногочисленную документацию класса StatelessElement. Обратите внимание, указанный в конструкторе параметр widget класса StatelessWidget передается родительскому классу ComponentElement, туда мы и перейдем:

Класс ComponentElement становится более обширным, так что я сделал скриншот начала этого класса. Это тоже абстрактный класс, что имеет смысл, поскольку он содержит тот самый необходимый для выполнения метод build(). Сейчас мы возвращаемся назад через иерархию классов, однако, мы еще посетим эти «промежуточные» классы через некоторое время. Сейчас мы должны обратить внимание на класс Element.

Опять-таки, мы смотрим только на начало этого класса. Он гораздо больше тех, что мы рассматривали ранее: в целом, он состоит из 90 методов и полей. В самом деле, довольно важный «элемент». Однако, мы пришли к цели. Что вы видите?

Класс Element реализует другой класс, BuildContext. В отличие от, к примеру, Java, любой класс в языке программирования Dart может быть использован, как интерфейс – вы просто добавляете его название в конце декларации класса сразу после ключевого слова implements.

Конечно, если только ваш класс не является абстрактным, вы должны реализовать все методы и поля, составляющие этот класс. В случае с классом Elements, мы реализуем класс BuildContext со всей его составляющей – не удивительно, что теперь мы видим огромное количество полей и методов. После выполнения этой непростой задачки, мы наконец-то можем заключить, что, как и во всех ориентированных на объект языках, объект типа Element может быть передан функциям или конструкторам как объект типа BuildContext.

Документация Flutter поясняет, что BuildContext было решено использовать в качестве интерфейса с целью препятствия прямому манипулированию объектами Element – что бы это ни значило. К тому же, кто вообще захочет разбираться с 90 методами и полями класса?

Так что, теперь вы знаете, что при размещении контрольной точки в своей любимой IDE прямо в строке метода build(), параметр контекста (context), переданный этму методу (в случае StatelessWidget) – тот самый объект StatelessElement, создание которого мы видели в скриншоте StatelessWidget. Обратите внимание, что это касается и StatefulWidget – только с объектом StatefulElement. Оба эти типа виджетов представлены ниже:

Помните, что у каждого виджета есть свой объект Element (или же объект BuildContext). Element/BuildContext может принадлежать только одному виджету. Когда вы только начинали изучать Flutter, скорее всего, вы пришли к выводу, что все виджеты, составляющие ваше приложение, расположены в древообразном порядке, и это правда, только не совсем. Это их сообщающиеся объекты Element, связанные друг с другом. Каждый элемент (контекст) содержит отсылку на свой «родительский» элемент, который создает тот самый элемент (контекст) при запуске приложения Flutter. Можно сказать, что приложение Flutter состоит из связанных друг с другом объектов BuildContext, которые и составляют «древо объектов BuildContext». Они остаются неизменными в течение всего цикла приложения, в то время как их аналоги-«виджеты» могут быть удалены и воссозданы вновь и вновь.

Поместим это в контекст

Я воспользуюсь приложением из другой статьи для демонстрации «процесса наполнения», задействованного при запуске приложения и того, как каждый виджет включает в себя свой собственный аналог элемента (контекста). Домашний экран приложения со всем его кодом представлен ниже. На первый взгляд, мы имеем десять виджетов:

main.dart

Скриншоты ниже показывают, что происходит при вызове метода createElement для StatelessWidget. Как только это происходит, в «родительском» методе элемента, inflateWidget(), создается соответствующий объект элемента виджета. На среднем скриншоте отображена эта функция и показано, как недавно созданный объект элемента вызывает свою собственный метод mount(). Это происходит там же, где и «родительский» элемент привязывается к полю дочернего экземпляра _parent, добавляясь к древу рендеринга. Каждому виджету соответствует свой элемент/контекст объекта.

А теперь с Context

При помощи ссылки на объекты BuildContext у вас появляется все необходимое для возврата к дереву, чтобы получить все «выше» созданные экземпляры Widgets и объекты State. К примеру, все эти функции «of» перечислены в начале этой статьи, и каждая из них содержит функцию из BuidContext, реализованную и определенную в классе Element:

Тема оформления

Напоследок, разберем еще парочку более популярных исполнений. Работая с Flutter, вы обнаружите, что статичная функция Theme.of() используется несколько раз при работе фреймворка. Ниже слева есть скриншот, показывающий весьма распространенное действие – получение объекта ThemeData для применения определенного стиля для виджета Text. На скриншоте справа отображен определенный в классе BuidContext метод dependInheritWidgetOfExactType, используемый для поиска определенного «типа InheritedWidget» в объекте Map под названием _inheritedWidgets.

Ищем Scaffold

В приложениях Flutter также весьма распространен метод Scaffold.of(). Он содержит прочие методы, определенные в классе BuildContext и реализованные в классе Element. Также он идет вверх через весь список связанных объектов Element, чтобы найти ранее определенный объект State. Практически целый цикл связан с возвращением через связанный список объектов Element ( каждый из них связан со своим «родителем») в целях поисках виджета определенного типа. В нашем случае выполняется поиск Scaffold, определенного ранее в древе рендеринга, а затем возвращается его связанный объект State под названием ScaffoldState.

Пока что остановимся на этом. Класс Element реализует класс BuildContext, и, возможно, чуть позже мы обратимся к ряду других функций, с помощью которых можно извлекать множество «элементов» вашего приложения, если они уже были созданы и упомянуты как часть древа рендеринга.

До встречи!

Другие статьи автора оригинального текста Greg Perry

Подборка заметок