В Flutter ключ объекта существует главным образом для различения и поддержания состояния виджета; это один из маркеров "повторного использования" компонентов в дереве отрисовки. Это было упомянуто ранее в статье "Сравнение реализации алгоритма diff при обновлении UI в Flutter и Compose", где говорилось, что ключ играет важную роль в производительности Flutter, так как его основной задачей является повышение эффективности повторного использования дерева элементов, например, снижение количества сравнений виджетов во время этапа сопоставления.
Кроме того, ключи могут повысить эффективность управления списками, таких как AnimatedList
, ListView
. Присваивая ключ каждому элементу списка, Flutter может более эффективно определять моменты добавления, удаления или обновления элементов и выполнять анимацию. В этом случае ключ гарантирует, что каждый элемент сохраняет своё состояние даже при сортировке списка.
В большинстве случаев ключ не требуется для бесштатных виджетов, а по умолчанию ключ будет равен null, если он не указан явно:
Это значит, что без ключа фреймворк обычно проверяет только
runtimeType
для решения вопроса "повторного использования". Вот старый пример из официальной документации, показывающий виджет StatelessColorfulTile
, который представляет собой бесштатный StatelessWidget
, отображающий квадрат размером 200x200 со случайным цветом. При нажатии кнопки справа внизу происходит перемещение двух таких квадратов, и можно заметить, что они корректно меняются местами:
При отсутствии ключа дерево элементов просто сравнивает runtimeType, чтобы определить возможность повторного использования. Так как используется прямой экземпляр виджета StatelessColorfulTile, то после перемещения элемента достаточно лишь обновить цвет нового экземпляра:
Однако, если мы преобразуем этот виджет в StatefulWidget, то при нажатии кнопки справа внизу цвета квадратов уже не будут меняться:
При этом цвет color
сохраняется в состоянии State
, поэтому при смене позиции виджета Widget
, если условие runtimeType
выполняется, происходит переиспользование элемента Element
. Однако, поскольку состояние State
хранится внутри элемента Element
, это приводит к тому, что цвет не обновляется должным образом:
Однако, если мы добавим ключи Key
двум виджетам StatefulWidget
, можно заметить, что они могут успешно переключаться, так как условие canUpdate
теперь также проверяет ключи:
То есть, после добавления ключей новый виджет с ключом может быть найден среди старых элементов, что позволяет обновить положение элемента и перерenderить объект RenderObject
. Два элемента, сохранив своё состояние, меняются местами в дереве и обновляются, обеспечивая эффект переключения:
Из этого простого примера становится очевидной роль ключей при наличии состояния. Конечно, в Flutter существует множество типов ключей, но их можно условно разделить на две категории: локальные ключи (Local Keys
) и глобальные ключи (Global Keys
).
Как следует из названия, область действия этих ключей различна. Например, если мы добавим Padding
к StatelessColorfulTile
и затем переключимся, можно заметить, что элемент будет постоянно перестраиваться:
Это связано с тем, что в контейнере Row
находятся два Padding
, а сами Padding
не имеют ключей. Поэтому при выполнении условия runtimeType
происходит переиспользование элемента:
Для StatelessColorfulTile
, который находится внутри Padding
, который не является многими детьми, когда условие canUpdate
равно false
, Flutter считает необходимым создать его заново:
Отсюда становится очевидным понятие локальных ключей: они используются для идентификации виджетов внутри одного родительского виджета, но не могут использоваться для идентификации виджетов вне данного родительского виджета.Кроме того, становится очевидным различие между стратегиями обновления для элементов с множеством детей и элементов с одним ребенком. Кроме того, мы можем заметить существование 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, явное указание ключа не требуется:
Помимо локальных ключей, Flutter предоставляет специальный глобальный ключ (Global Key), который позволяет разработчику уникально идентифицировать Widget в дереве виджетов и обеспечивает доступ ко всему дереву виджетов (Build Context/Element) и состояниям (State):
Здесь «уникальность» относится к текущей кадре.
Например, в предыдущем примере, заменив локальные ключи на глобальные ключи, можно заметить, что хотя StatelessColorfulTile
остаётся вторым потомком Padding
, при переключении он больше не создаётся заново, поэтому цвет не изменяется:
Это происходит потому, что при вызове метода updateChild
, даже если логика попытается создать новый Element через inflateWidget
, благодаря глобальному ключу, этот Element будет получен из глобального хранилища, что позволит повторно использовать его:
Из этого можно сделать вывод, что если Element перемещается или удаляется в рамках одной кадры и имеет глобальный ключ, он всё равно может быть активирован снова.Таким образом, глобальный ключ не только служит для идентификации виджета, но также позволяет сохранять состояние элемента, состояния и связанных объектов рендера внутри одного кадра, даже если они перемещаются или удаляются.
Кроме того, с помощью глобального ключа можно получить доступ к соответствующему BuildContext и данным состояния, а также напрямую использовать GlobalKey для управления навигацией в MaterialApp
.
Так как GlobalKey так удобна в использовании, какие проблемы она может иметь? На самом деле, это уже указано в комментариях:
При использовании GlobalKey могут возникнуть ситуации, когда потребуется заново установить родителя [Element], что приведёт к вызову метода [State.deactivate] для всех связанных [State] и всех его потомков, а также к принудительному перестроению всех компонентов, зависящих от [InheritedWidget].
Это проявляется в следующих двух участках кода:
_retakeInactiveElement
могут быть вызваны методы deactivate для всех связанных состояний;_activateWithParent
вызывает метод activate для элемента, что заставляет перестроиться всем зависимым от [InheritedWidget] компонентам через вызов didChangeDependencies.Кроме того, при использовании GlobalKey есть некоторые моменты, требующие внимания, такие как:> Нельзя часто создавать GlobalKey
, поскольку её "жизненный цикл" должен быть схож с жизненным циклом объекта State
. Создание нового GlobalKey
приводит к тому, что состояние старого ключа теряется, и создаётся новое поддерево для нового ключа. Частое создание приводит к потере состояния и снижению производительности.
Ранее мы подробно рассмотрели роль ключей и их функциональность. Предложение текущего Pull Request заключается в упрощении реализации локальных ключей, что можно заметить в предыдущих реализациях, где существовало несколько типов локальных ключей, но некоторые из них имели относительно повторяющиеся функции:
В Pull Request #159225 планируется замена типа Key
на Object
, чтобы "ликвидировать" эти перекрывающиеся локальные ключи и сделать API ключей более гибким:
Кроме того, помимо гибкости и упрощения, данное предложение также поможет повысить производительность, особенно если сравнить с текущими локальными ключами, которые отличаются от расширяемых типов Dart, например, использование ValueKey()
немного увеличивает затраты на обёртывание данных. После внедрения данного предложения ситуация будет выглядеть примерно так, что позволит немного улучшить производительность:
На самом деле большинство людей используют только
ValueKey
при работе сLocalKey
. Конечно, эта pull request в целом относится к глобальным изменениям в основе, а на данный момент предложение выглядит как временно отложенный, но даже если оно будет реализовано, большинство верхних уровней Flutter-разработчиков этого, скорее всего, не заметят, так как большинство разработчиков Flutter не чувствительны к ключам:
Итак, предпочитаешь текущую классификацию Local Key
или предложенное понятие Object
?
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )