Learn Rust Coding · レッスン

アプリの状態を管理する

UIデータを保持して更新します。

レッスン 3/413 ステップ

「アプリの状態を管理する」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLearn Rust Coding学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Learn Rust Codingコースには全4レッスンが含まれています。

App構造体に状態を保持する

eguiでは、アプリケーションの状態はeframe::Appを実装する構造体に保持する、通常のRustデータです。専用のストアや、フレームワークが所有するモデルはありません。

フォームの入力値、選択項目、読み込んだデータ、フラグなど、必要なものはすべてフィールドに保持します。各フレームで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に結び付けたチェックボックスは、そのboolを直接表示・更新します。同じフレーム内の別の場所で読み取れば、更新後の値を確認できます。

ui.checkbox(&mut self.enabled, "Enabled");
if self.enabled {
    ui.label("Feature is on");
}

変更を遅延させる

微妙な借用の問題があります。Vecを反復処理しながら、その中から要素を削除したくなることがあります。しかし、共有借用が有効な間は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); }

enumで画面を管理する

複数画面のアプリでは、現在のビューをenum型のフィールドとして表します。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),
}

UI状態用のeguiメモリ

折りたたまれたセクション、スクロール位置、ドラッグ状態など、ドメインではなくUIだけに関係する状態もあります。eguiはこれらをウィジェットのIdをキーとしてctx.memoryに保存します。

直接操作することはほとんどありませんが、ctx.data_mutを使えば、アプリ構造体を複雑にせず、フレーム間で保持されるIDごとの小さな値を保存できます。

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));

チャネルを使ったバックグラウンド処理

遅いI/Oでupdateを決してブロックしないでください。60fpsのループが停止してしまいます。スレッドを起動し、std::sync::mpscチャネルで通信します。

ワーカーが結果を送り、updateは各フレームでtry_recvを使って受信側からブロックせずに取り出します。データが到着したときにUIを再開できるよう、ワーカーから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はフレームごとに実行されるため、そこで高コストな派生データを再計算しないようにします。結果をフィールドにキャッシュし、入力が変わったときだけ再計算します。

よくあるパターンは、Response::changed()でdirtyフラグを設定し、updateの先頭でdirtyの場合にだけキャッシュを再構築する方法です。

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を通して変更します。そのため、構造体が唯一の信頼できるデータソースになります。

反復処理中の変更は遅延させ、画面はenumで表現し、時間のかかる処理はチャネルを使ってスレッドに移し、eframeのストレージで状態を永続化します。dirtyフラグを使って派生データをキャッシュし、updateを軽く保ちます。次は、配布用にアプリをパッケージ化する方法を学びます。

無料で開始

AI チューターと学ぶ Rust — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
39
レッスン
144

よくある質問

「アプリの状態を管理する」レッスンは無料ですか?

はい。「アプリの状態を管理する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全4レッスンが含まれています。

「アプリの状態を管理する」で何を学びますか?

UIデータを保持して更新します。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Learn Rust Codingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「アプリの状態を管理する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLearn Rust Codingレッスンでコードを書いて実行できますか?

はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Immediate-Mode UIの基本
  2. ウィジェットとレイアウト
  3. アプリの状態を管理する
  4. デスクトップアプリをパッケージ化する
← Learn Rust Codingに戻る