1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-Key2.md 17 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Предложения по ключам виджета Flutter? Давайте поговорим подробнее о роли ключей

В Flutter ключ объекта существует главным образом для различения и поддержания состояния виджета; это один из маркеров "повторного использования" компонентов в дереве отрисовки. Это было упомянуто ранее в статье "Сравнение реализации алгоритма diff при обновлении UI в Flutter и Compose", где говорилось, что ключ играет важную роль в производительности Flutter, так как его основной задачей является повышение эффективности повторного использования дерева элементов, например, снижение количества сравнений виджетов во время этапа сопоставления.

image1

Кроме того, ключи могут повысить эффективность управления списками, таких как AnimatedList, ListView. Присваивая ключ каждому элементу списка, Flutter может более эффективно определять моменты добавления, удаления или обновления элементов и выполнять анимацию. В этом случае ключ гарантирует, что каждый элемент сохраняет своё состояние даже при сортировке списка.

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

image2

image3Это значит, что без ключа фреймворк обычно проверяет только runtimeType для решения вопроса "повторного использования". Вот старый пример из официальной документации, показывающий виджет StatelessColorfulTile, который представляет собой бесштатный StatelessWidget, отображающий квадрат размером 200x200 со случайным цветом. При нажатии кнопки справа внизу происходит перемещение двух таких квадратов, и можно заметить, что они корректно меняются местами:image4

При отсутствии ключа дерево элементов просто сравнивает runtimeType, чтобы определить возможность повторного использования. Так как используется прямой экземпляр виджета StatelessColorfulTile, то после перемещения элемента достаточно лишь обновить цвет нового экземпляра:

image5

Однако, если мы преобразуем этот виджет в StatefulWidget, то при нажатии кнопки справа внизу цвета квадратов уже не будут меняться:

image6

При этом цвет color сохраняется в состоянии State, поэтому при смене позиции виджета Widget, если условие runtimeType выполняется, происходит переиспользование элемента Element. Однако, поскольку состояние State хранится внутри элемента Element, это приводит к тому, что цвет не обновляется должным образом:

image

Однако, если мы добавим ключи Key двум виджетам StatefulWidget, можно заметить, что они могут успешно переключаться, так как условие canUpdate теперь также проверяет ключи:

image image

То есть, после добавления ключей новый виджет с ключом может быть найден среди старых элементов, что позволяет обновить положение элемента и перерenderить объект RenderObject. Два элемента, сохранив своё состояние, меняются местами в дереве и обновляются, обеспечивая эффект переключения:image

Из этого простого примера становится очевидной роль ключей при наличии состояния. Конечно, в Flutter существует множество типов ключей, но их можно условно разделить на две категории: локальные ключи (Local Keys) и глобальные ключи (Global Keys).

image

Как следует из названия, область действия этих ключей различна. Например, если мы добавим Padding к StatelessColorfulTile и затем переключимся, можно заметить, что элемент будет постоянно перестраиваться:

image

Это связано с тем, что в контейнере Row находятся два Padding, а сами Padding не имеют ключей. Поэтому при выполнении условия runtimeType происходит переиспользование элемента:

image

Для StatelessColorfulTile, который находится внутри Padding, который не является многими детьми, когда условие canUpdate равно false, Flutter считает необходимым создать его заново:

image

Отсюда становится очевидным понятие локальных ключей: они используются для идентификации виджетов внутри одного родительского виджета, но не могут использоваться для идентификации виджетов вне данного родительского виджета.Кроме того, становится очевидным различие между стратегиями обновления для элементов с множеством детей и элементов с одним ребенком. Кроме того, мы можем заметить существование Widget как «��置文件». Важно понимать, что в коде мы работаем с объектами типа tiles.insert(1, tiles.removeAt(0));, то есть экземплярами Widget. Хотя сам экземпляр Widget не меняется, на уровне Element он может «пересоздаваться», а поскольку цвет хранится в State, это приведёт к случайной перезаписи при каждом пересоздании Element.

Перевод:

Кроме того, становится очевидным различие между стратегиями обновления для элементов с множеством детей и элементов с одним ребенком. Кроме того, мы можем заметить существование Widget как «конфигурационного файла». Важно понимать, что в коде мы работаем с объектами типа tiles.insert(1, tiles.removeAt(0));, то есть экземплярами Widget. Хотя сам экземпляр Widget не меняется, на уровне Element он может «пересоздаваться», а поскольку цвет хранится в State, это приведёт к случайной перезаписи при каждом пересоздании Element.Наконец, как показано на следующих рисунках, для локальных ключей (Local Keys) такой подход возможен:

Поэтому в комментариях к ключам Widget указано, что обычно, если Widget является единственным потомком другого Widget, явное указание ключа не требуется:

GlobalKey

Помимо локальных ключей, Flutter предоставляет специальный глобальный ключ (Global Key), который позволяет разработчику уникально идентифицировать Widget в дереве виджетов и обеспечивает доступ ко всему дереву виджетов (Build Context/Element) и состояниям (State):

Здесь «уникальность» относится к текущей кадре.

Например, в предыдущем примере, заменив локальные ключи на глобальные ключи, можно заметить, что хотя StatelessColorfulTile остаётся вторым потомком Padding, при переключении он больше не создаётся заново, поэтому цвет не изменяется:

Это происходит потому, что при вызове метода updateChild, даже если логика попытается создать новый Element через inflateWidget, благодаря глобальному ключу, этот Element будет получен из глобального хранилища, что позволит повторно использовать его:

Из этого можно сделать вывод, что если Element перемещается или удаляется в рамках одной кадры и имеет глобальный ключ, он всё равно может быть активирован снова.Таким образом, глобальный ключ не только служит для идентификации виджета, но также позволяет сохранять состояние элемента, состояния и связанных объектов рендера внутри одного кадра, даже если они перемещаются или удаляются.

Кроме того, с помощью глобального ключа можно получить доступ к соответствующему BuildContext и данным состояния, а также напрямую использовать GlobalKey для управления навигацией в MaterialApp.

image image

Так как GlobalKey так удобна в использовании, какие проблемы она может иметь? На самом деле, это уже указано в комментариях:

При использовании GlobalKey могут возникнуть ситуации, когда потребуется заново установить родителя [Element], что приведёт к вызову метода [State.deactivate] для всех связанных [State] и всех его потомков, а также к принудительному перестроению всех компонентов, зависящих от [InheritedWidget].

image

Это проявляется в следующих двух участках кода:

  • Внутри _retakeInactiveElement могут быть вызваны методы deactivate для всех связанных состояний;
  • _activateWithParent вызывает метод activate для элемента, что заставляет перестроиться всем зависимым от [InheritedWidget] компонентам через вызов didChangeDependencies.

image

Кроме того, при использовании GlobalKey есть некоторые моменты, требующие внимания, такие как:> Нельзя часто создавать GlobalKey, поскольку её "жизненный цикл" должен быть схож с жизненным циклом объекта State. Создание нового GlobalKey приводит к тому, что состояние старого ключа теряется, и создаётся новое поддерево для нового ключа. Частое создание приводит к потере состояния и снижению производительности.

Предложения по изменениям

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

В Pull Request #159225 планируется замена типа Key на Object, чтобы "ликвидировать" эти перекрывающиеся локальные ключи и сделать API ключей более гибким:

Кроме того, помимо гибкости и упрощения, данное предложение также поможет повысить производительность, особенно если сравнить с текущими локальными ключами, которые отличаются от расширяемых типов Dart, например, использование ValueKey() немного увеличивает затраты на обёртывание данных. После внедрения данного предложения ситуация будет выглядеть примерно так, что позволит немного улучшить производительность:

На самом деле большинство людей используют только ValueKey при работе с LocalKey. Конечно, эта pull request в целом относится к глобальным изменениям в основе, а на данный момент предложение выглядит как временно отложенный, но даже если оно будет реализовано, большинство верхних уровней Flutter-разработчиков этого, скорее всего, не заметят, так как большинство разработчиков Flutter не чувствительны к ключам:

image

Итак, предпочитаешь текущую классификацию Local Key или предложенное понятие Object?

Ссылки для справки:

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master