Управление состоянием приложения
Храните и обновляйте данные интерфейса.
«Управление состоянием приложения» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Состояние в структуре App
В egui состояние приложения — это обычные данные Rust, хранящиеся в структуре, реализующей eframe::App. Специального хранилища или модели, которой владеет фреймворк, нет.
Поля содержат всё: вводимые в форму данные, выбранные значения, загруженные данные и флаги. В каждом кадре update напрямую читает и изменяет эти поля через &mut self.
struct App {
query: String,
results: Vec<Item>,
selected: Option<usize>,
loading: bool,
}Вывод реализации Default
eframe создаёт Ваше приложение в замыкании, переданном в run_native. Самый простой вариант — использовать #[derive(Default)] и создать приложение с помощью App::default().
Если начальные значения не должны быть стандартными, реализуйте Default вручную или добавьте конструктор, принимающий CreationContext для настройки, например шрифтов или сохранения данных.
impl App {
fn new(cc: &eframe::CreationContext) -> Self {
let mut app = Self::default();
app.dark = cc.egui_ctx.style().visuals.dark_mode;
app
}
}Единственный источник истины
Поскольку виджеты заимствуют &mut у соответствующих полей, структура всегда является единственным источником истины. Нет отдельного состояния представления, которое нужно поддерживать синхронным.
Флажок, связанный с &mut self.enabled, напрямую отражает это логическое значение и изменяет его — прочитайте его в любом другом месте того же кадра и увидите актуальное значение.
ui.checkbox(&mut self.enabled, "Enabled");
if self.enabled {
ui.label("Feature is on");
}Отложенные изменения
Есть одна тонкость с заимствованием: часто нужно пройти по Vec и одновременно удалить из него элемент. Нельзя изменять вектор, пока существует общее заимствование.
Решение — записать желаемое действие в локальную переменную во время цикла, а затем применить его после завершения цикла.
let mut to_remove = None;
for (i, item) in self.items.iter().enumerate() {
ui.horizontal(|ui| {
ui.label(&item.name);
if ui.button("x").clicked() { to_remove = Some(i); }
});
}
if let Some(i) = to_remove { self.items.remove(i); }Экраны на основе перечисления
В приложениях с несколькими экранами представьте текущий экран полем-перечислением. Метод update сопоставляет с ним значение, чтобы определить, что рисовать.
Это делает навигацию явной и исчерпывающей: компилятор заставляет Вас обработать каждый экран, устраняя целые классы ошибок маршрутизации.
enum Screen { Home, Settings, About }
match self.screen {
Screen::Home => self.draw_home(ui),
Screen::Settings => self.draw_settings(ui),
Screen::About => self.draw_about(ui),
}Память egui для состояния интерфейса
Некоторое состояние относится только к интерфейсу, а не к Вашей предметной области: свёрнутые разделы, позиции прокрутки, состояние перетаскивания. egui хранит его в ctx.memory, используя идентификатор виджета Id как ключ.
Обычно Вам не нужно обращаться к этому напрямую, но ctx.data_mut позволяет сохранять небольшие значения для каждого идентификатора, которые сохраняются между кадрами и не загромождают структуру приложения.
let id = egui::Id::new("my_toggle");
let mut open = ctx.data_mut(|d| d.get_temp::<bool>(id).unwrap_or(false));
ui.checkbox(&mut open, "Open");
ctx.data_mut(|d| d.insert_temp(id, open));Фоновая работа с каналами
Никогда не блокируйте update медленными операциями ввода-вывода — это замораживает цикл с частотой 60 кадров в секунду. Запустите поток и обменивайтесь данными через канал std::sync::mpsc.
Рабочий поток отправляет результаты обратно, а update каждый кадр извлекает их из приёмника без блокировки с помощью try_recv. Вызывайте ctx.request_repaint() из рабочего потока, чтобы интерфейс обновлялся при поступлении данных.
if let Ok(msg) = self.rx.try_recv() {
self.results = msg;
self.loading = false;
}Запуск рабочего потока
Клонируйте Context и отправителя в поток. Клонирование выполняется быстро — внутри используется Arc — и позволяет рабочему потоку запрашивать перерисовку.
Переместите владение входными данными работы в замыкание, чтобы удовлетворить требованиям проверяющего заимствования и избежать проблем со временем жизни.
let tx = self.tx.clone();
let ctx = ctx.clone();
std::thread::spawn(move || {
let data = expensive_load();
let _ = tx.send(data);
ctx.request_repaint();
});Сохранение состояния
eframe может сериализовать состояние приложения между запусками. Включите возможность persistence, добавьте производные реализации serde::Serialize / Deserialize и реализуйте App::save.
При запуске прочитайте сохранённое состояние из cc.storage в конструкторе, используя значения по умолчанию, если ничего не сохранено.
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
// in new():
if let Some(s) = cc.storage {
if let Some(app) = eframe::get_value(s, eframe::APP_KEY) { return app; }
}Как не перегружать update
Поскольку update выполняется каждый кадр, не пересчитывайте в нём дорогие производные данные. Кэшируйте результаты в полях и пересчитывайте их только при изменении входных данных.
Распространённый подход: отслеживать флаг dirty, устанавливаемый методом Response::changed(), и перестраивать кэши в начале update только при установленном флаге.
if self.dirty {
self.filtered = self.filter_items();
self.dirty = false;
}
for item in &self.filtered { ui.label(&item.name); }Владение обеспечивает предсказуемость
Главная идея такова: egui опирается на владение в Rust, а не на фреймворки для управления состоянием. Ваша структура владеет данными, &mut self предоставляет исключительный доступ на каждый кадр, а потоки обмениваются данными через каналы.
Здесь нет скрытых подписок или графов обратных вызовов, поэтому понять, когда и как меняется состояние, можно локально и явно.
Быстрая проверка
Как следует выполнять медленный сетевой запрос?
Итоги
Состояние приложения представлено обычными полями структуры eframe::App, которые изменяются через &mut self в каждом кадре. Поэтому структура является единственным источником истины.
Откладывайте изменения во время перебора, моделируйте экраны с помощью перечислений, выносите медленные операции в потоки и обменивайтесь данными через каналы, а состояние сохраняйте с помощью хранилища eframe. Поддерживайте update быстрым, кэшируя производные данные за флагами изменения. Далее: упаковка приложения для распространения.
Часто задаваемые вопросы
Урок «Управление состоянием приложения» бесплатный?
Да — полный текст урока «Управление состоянием приложения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Управление состоянием приложения»?
Храните и обновляйте данные интерфейса. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Управление состоянием приложения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Основы интерфейса немедленного режима
- Виджеты и компоновка
- Управление состоянием приложения
- Сборка настольного приложения