Тема
Состояние и хранилище
Мини-апп умеет хранить данные без своего сервера. Есть два независимых механизма, и они решают разные задачи:
setState | setStorage | |
|---|---|---|
| Где живёт | в памяти экрана | в Postgres, на пользователя и апп |
| Переживает перезапуск | нет | да |
| Обновление UI | на месте, скролл сохраняется | нужен reload/openPage |
| Плейсхолдер | {{state.key}} | {{storage.key}} |
| Зачем | переключатели, табы, счётчики на экране | чеклисты, настройки, прогресс |
Оба доступны и hosted-, и remote-аппам, но storage особенно полезен hosted-аппам: он даёт персистентность там, где сервера нет вообще.
Реактивное состояние: appStateScope + setState
appStateScope — это граница локального состояния. Он задаёт начальные значения и оборачивает поддерево, внутри которого работают плейсхолдеры {{state.<key>}}. Экшен setState меняет значение, и перерисовывается только это поддерево — экран не перезагружается, скролл и позиции остаются на месте. Состояние живёт в памяти и при выходе из экрана сбрасывается.
json
{
"type": "appStateScope",
"initial": { "count": 0 },
"child": {
"type": "column",
"crossAxisAlignment": "start",
"children": [
{ "type": "text", "data": "Счёт: {{state.count}}" },
{ "type": "sizedBox", "height": 12 },
{ "type": "appButton", "label": "+5",
"onPressed": { "actionType": "setState", "key": "count", "add": 5 } }
]
}
}Поля setState
key— имя значения внутри ближайшегоappStateScope.value— задать литерал (строку, число, bool, объект).add— прибавить число к текущему значению (если значения нет, считается нулём). Используется вместоvalue.action— необязательный экшен, который выполнится после изменения.
json
{ "actionType": "setState", "key": "tab", "value": "week",
"action": { "actionType": "hapticFeedback", "style": "selection" } }Типичный приём — локальные табы без обращения к серверу:
json
{ "type": "appStateScope", "initial": { "tab": "today" }, "child": {
"type": "column", "children": [
{ "type": "appSegmentedControl", "options": [
{ "label": "Сегодня", "onSelected": { "actionType": "setState", "key": "tab", "value": "today" } },
{ "label": "Неделя", "onSelected": { "actionType": "setState", "key": "tab", "value": "week" } }
]},
{ "type": "text", "data": "Активная вкладка: {{state.tab}}" }
]
}}setState или openPage
setState хорош, когда данные уже на экране и нужно показать другой срез. Если контент надо догрузить с сервера — это networkRequest или openPage.
Персональное хранилище: setStorage
setStorage пишет одно значение в постоянное хранилище — своё у каждого пользователя в каждом аппе. Значения читаются как {{storage.<key>}} и подставляются на следующем рендере, поэтому после записи обнови UI: reload для текущего экрана или openPage для перехода.
json
{ "type": "text", "data": "Серия: {{storage.streak}} дней" }json
{ "actionType": "multiAction", "actions": [
{ "actionType": "setStorage", "key": "streak", "value": 7 },
{ "actionType": "reload" }
]}"value": nullудаляет ключ.- Значения подставляются при рендере — после записи нужен
reload/openPage. - При запуске аппа всё хранилище загружается разом, так что плейсхолдеры на стартовом экране резолвятся сразу.
Лимиты хранилища
| Ограничение | Значение |
|---|---|
| Ключей на пользователя на апп | 50 |
| Размер значения | ≤ 8 КБ (любой JSON) |
| Формат ключа | ^[a-zA-Z0-9_.\-]{1,64}$ (буквы, цифры, _ . -, 1–64 символа) |
Превышение лимита ключей или размера значения — ошибка записи, апп должен это пережить. Хранилище не предназначено под большие данные: держи в нём флаги, счётчики и компактные настройки, а не списки на тысячи элементов.
Пример: счётчик без сервера
json
{
"type": "appCard",
"child": {
"type": "column",
"crossAxisAlignment": "start",
"children": [
{ "type": "text", "data": "Выпито воды: {{storage.water}} ст." },
{ "type": "sizedBox", "height": 12 },
{ "type": "appButton", "label": "+1 стакан", "expanded": true,
"onPressed": { "actionType": "multiAction", "actions": [
{ "actionType": "setStorage", "key": "water", "value": 1 },
{ "actionType": "reload" }
]}}
]
}
}Чтобы увеличивать счётчик, считай новое значение на стороне логики экрана (например, в appStateScope через setState add, а итог фиксируй в setStorage) — само хранилище умеет только записать заданное значение.
Плейсхолдеры
Под капотом и {{state.*}}, и {{storage.*}} — это реестр подстановок Stac. Туда же пишет встроенный экшен setValue (ключ {{key}} без префикса) — низкоуровневый аналог, если нужен просто временный регистр значения в пределах экрана.
Плейсхолдеры считаются при рендере
Значение подставляется в момент отрисовки виджета. setState сам перерисовывает свой scope; для setStorage и setValue перерисовку запускаешь ты (reload / openPage).