0Pricing
Learn Rust Coding · บทเรียน

จัดการสถานะแอป

เก็บและอัปเดตข้อมูล UI

จัดการสถานะแอป เป็นบทเรียน Learn Rust Coding ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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,
}

การสร้างค่าเริ่มต้น

eframe สร้างแอปของคุณใน closure ที่ส่งให้ 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 ขณะต้องการลบสมาชิกออก คุณไม่สามารถแก้ไขเวกเตอร์ในขณะที่ยังมีการยืมแบบอ่านร่วมอยู่ได้

วิธีแก้คือบันทึกการกระทำที่ต้องการไว้ในตัวแปรเฉพาะที่ระหว่างลูป แล้วนำไปใช้หลังจบลูป

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),
}

หน่วยความจำ egui สำหรับสถานะ UI

สถานะบางอย่างเกี่ยวข้องกับ UI ล้วน ๆ ไม่ใช่โดเมนของคุณ เช่น ส่วนที่ยุบ ตำแหน่งการเลื่อน และสถานะการลาก egui จัดเก็บสิ่งเหล่านี้ไว้ใน ctx.memory โดยมีวิดเจ็ต Id เป็นกุญแจ

คุณแทบไม่จำเป็นต้องแตะต้องมันโดยตรง แต่ 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));

งานเบื้องหลังด้วยแชนเนล

อย่าบล็อก update ด้วยการรับส่งข้อมูลที่ช้า เพราะจะทำให้ลูป 60fps หยุดชะงัก ให้สร้างเธรดแล้วสื่อสารผ่านแชนเนล std::sync::mpsc

ตัวทำงานจะส่งผลลัพธ์กลับมา ส่วน update จะดึงข้อมูลจากตัวรับแบบไม่บล็อกด้วย try_recv ในแต่ละเฟรม เรียก ctx.request_repaint() จากตัวทำงานเพื่อปลุก UI เมื่อข้อมูลมาถึง

if let Ok(msg) = self.rx.try_recv() {
    self.results = msg;
    self.loading = false;
}

การสร้างตัวทำงาน

โคลน Context และตัวส่งเข้าไปในเธรด การโคลนมีค่าใช้จ่ายต่ำ เพราะภายในเป็น Arc และช่วยให้ตัวทำงานร้องขอการวาดใหม่ได้

ย้ายการเป็นเจ้าของอินพุตของงานเข้าไปใน closure เพื่อให้ผ่านตัวตรวจสอบการยืมและหลีกเลี่ยงปัญหาอายุการใช้งาน

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 มอบสิทธิ์เข้าถึงแบบผูกขาดในแต่ละเฟรม และเธรดสื่อสารกันผ่านแชนเนล

ไม่มีการสมัครรับข้อมูลที่ซ่อนอยู่หรือกราฟ callback ดังนั้นการทำความเข้าใจว่าสถานะเปลี่ยนเมื่อใดและอย่างไรจึงยังคงอยู่ในขอบเขตที่ชัดเจนและตรวจสอบได้

ตรวจสอบความเข้าใจ

คุณควรดำเนินการรับข้อมูลจากเครือข่ายที่ช้าอย่างไร

สรุป

สถานะแอปคือฟิลด์ปกติบนโครงสร้าง eframe::App ซึ่งถูกแก้ไขผ่าน &mut self ในแต่ละเฟรม ทำให้โครงสร้างเป็นแหล่งข้อมูลจริงเพียงแหล่งเดียว

เลื่อนการแก้ไขค่าระหว่างการวนซ้ำ จำลองหน้าจอด้วย enum แยกงานที่ช้าไปยังเธรดด้วยแชนเนล และเก็บสถานะถาวรด้วยที่เก็บข้อมูลของ eframe ทำให้ update มีค่าใช้จ่ายต่ำด้วยการแคชข้อมูลอนุพันธ์ไว้เบื้องหลังแฟล็กสกปรก ต่อไป: การจัดแพ็กเกจแอปเพื่อเผยแพร่

คำถามที่พบบ่อย

บทเรียน “จัดการสถานะแอป” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “จัดการสถานะแอป” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “จัดการสถานะแอป”

เก็บและอัปเดตข้อมูล UI คุณปฏิบัติ Learn Rust Coding ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Learn Rust Coding หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Learn Rust Coding บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “จัดการสถานะแอป” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Learn Rust Coding นี้ได้ไหม

ได้ บทเรียน Learn Rust Coding ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน UI แบบโหมดทันที
  2. วิดเจ็ตและการจัดวาง
  3. จัดการสถานะแอป
  4. จัดแพ็กเกจแอปเดสก์ท็อป
← กลับไปที่ Learn Rust Coding