Введение
Статья 479 кратко показала пример привязки (width: container.width * 0.5) — в этой статье разберём механизм привязок подробнее: как движок QML отслеживает зависимости и определяет, когда нужно пересчитать значение, и какие практические соображения важны для эффективного использования привязок.
Концепция
Привязка свойства — выражение JavaScript (статья 483), записанное как значение свойства, которое движок QML автоматически переоценивает каждый раз, когда любое из значений, от которых это выражение зависит, изменяется — движок строит граф зависимостей автоматически, анализируя, какие свойства каких объектов упоминаются в выражении привязки, и подписывается на сигналы изменения именно этих свойств. Это принципиально отличается от простого, разового присваивания значения — присваивание (width = 100) выполняется один раз и не пересматривается позже, тогда как привязка (width: someOtherItem.width) продолжает действовать постоянно, обновляя width при каждом изменении someOtherItem.width, пока сама эта привязка не будет явно заменена другим присваиванием или привязкой.
Пример кода: Привязки свойств (property bindings)
import QtQuick
Rectangle {
id: root
width: 400; height: 200
property int itemCount: 5 // обычное, "хранимое" свойство
Rectangle {
id: dynamicBox
// Привязка — автоматически пересчитывается при изменении ЛЮБОГО из участвующих свойств
width: root.width / root.itemCount
height: root.height
color: "coral"
}
// Изменение itemCount автоматически пересчитает dynamicBox.width БЕЗ какого-либо явного кода для этого
Slider {
from: 1; to: 10
onValueChanged: root.itemCount = Math.round(value)
}
}
// Демонстрация разницы между привязкой и разовым присваиванием
Rectangle {
id: root
property int baseSize: 100
Rectangle {
id: boundBox
width: root.baseSize * 2 // ПРИВЯЗКА — будет обновляться при изменении root.baseSize
}
Rectangle {
id: assignedBox
Component.onCompleted: {
width = root.baseSize * 2 // РАЗОВОЕ присваивание в JavaScript — больше НЕ обновится
// при последующем изменении root.baseSize!
}
}
}
// Разрыв привязки явным присваиванием — частый источник путаницы
Rectangle {
id: box
width: parent.width / 2 // изначально это ПРИВЯЗКА
MouseArea {
anchors.fill: parent
onClicked: box.width = 200
// ^ это присваивание ПОЛНОСТЬЮ заменяет привязку — после клика box.width больше
// НЕ будет реагировать на изменения parent.width, оставаясь зафиксированным на 200
}
}
Пояснения к коду
dynamicBox.width: root.width / root.itemCount — привязка, зависящая сразу от двух свойств (root.width и root.itemCount) — движок QML отслеживает оба, и изменение любого из них (в том числе через Slider.onValueChanged, изменяющий itemCount) автоматически вызывает пересчёт dynamicBox.width без необходимости писать явный код синхронизации. Сравнение boundBox (объявленная привязка) и assignedBox (присваивание внутри Component.onCompleted) демонстрирует ключевое практическое различие — несмотря на одинаковое первоначальное вычисленное значение, только boundBox продолжит реагировать на будущие изменения root.baseSize, тогда как assignedBox.width навсегда останется тем значением, что было вычислено в момент выполнения присваивания. Третий пример показывает частую, неочевидную для новичков ловушку — присваивание значения свойству, ранее имевшему привязку, не «добавляет» дополнительное условие к существующей привязке, а полностью её заменяет и отключает, что для разработчика, ожидающего сохранения исходного «динамического» поведения, может стать источником путаницы.
Подводные камни
- Случайный разрыв привязки явным императивным присваиванием значения тому же свойству, как показано в примере с
box.width = 200— это одна из самых частых, неочевидных для новичков ошибок в QML, и разработчик, ожидающий, что свойство продолжит реагировать на изменения, обнаруживает (часто не сразу), что оно «застряло» на последнем явно присвоенном значении; явное понимание этого поведения — необходимая часть владения QML. - Создание циклических зависимостей привязок (свойство A зависит от свойства B, которое зависит от свойства A) — это может привести либо к ошибке движка QML при обнаружении явного цикла, либо к неопределённому, труднопредсказуемому поведению (бесконечный цикл пересчёта или использование «замороженного» промежуточного значения), и привязки должны образовывать направленный, ацикличный граф зависимостей.
- Чрезмерно сложные, дорогие в вычислении выражения привязок, пересчитываемые при каждом изменении часто меняющегося зависимого свойства (например, привязка, зависящая от позиции мыши, обновляемой много раз в секунду при движении) — каждый пересчёт привязки имеет свою вычислительную стоимость, и для производительности (статья 494) важно следить, чтобы выражения привязок, зависящие от часто изменяющихся свойств, оставались дешёвыми в вычислении, перенося более дорогую логику в JavaScript-функции, вызываемые реже или асинхронно.
- Предположение, что привязки автоматически «оптимизируют» неэффективный, избыточно сложный код, освобождая разработчика от необходимости думать о производительности — хотя механизм привязок сам по себе эффективен (пересчёт происходит только при реальном изменении зависимостей, а не постоянно), сложность и стоимость самого выражения привязки целиком остаётся на ответственности разработчика, и привязки не являются «бесплатной» заменой продуманной, эффективной логики там, где такая логика реально нужна.