กลยุทธ์การแคชใน Flutter สำหรับแคชท้องถิ่น ข้อมูลล้าสมัย และกฎการรีเฟรช: เก็บอะไร เมื่อไรต้องหมดอายุ และทำอย่างไรให้หน้าจอสอดคล้องกัน

การแคชในแอปมือถือหมายถึงการเก็บสำเนาของข้อมูลไว้ใกล้ (ในหน่วยความจำหรือบนเครื่อง) เพื่อให้หน้าจอถัดไปเรนเดอร์ทันทีแทนที่จะรอเครือข่าย ข้อมูลนั้นอาจเป็นรายการ, โปรไฟล์ผู้ใช้, หรือผลการค้นหา
ส่วนที่ยากคือข้อมูลแคชมักจะ "ผิดเล็กน้อย" ผู้ใช้สังเกตได้เร็ว: ราคาที่ไม่อัพเดต, จำนวน notification ที่ค้าง, หรือหน้ารายละเอียดที่ยังแสดงข้อมูลเก่าแม้เพิ่งแก้ไขไป สิ่งที่ทำให้ดีบั๊กยากคือจังหวะเวลา: endpoint เดียวกันอาจดูถูกต้องหลังการ pull-to-refresh แต่ผิดหลังการย้อนกลับ, resume แอป, หรือสลับบัญชี
มีการแลกเปลี่ยนจริงจัง: ถ้าดึงข้อมูลใหม่ตลอด หน้าจอจะรู้สึกช้าและกระพริบ และเปลืองแบตและดาต้า ถ้าแคชเยอะ แอปจะรู้สึกเร็ว แต่ผู้ใช้หยุดเชื่อถือสิ่งที่เห็น
เป้าหมายง่ายๆ ช่วยได้: ทำให้ความสดทำนายได้ ตัดสินใจว่าแต่ละหน้าจออนุญาตแสดงอะไร (สด เล็กน้อยล้าสมัย หรือออฟไลน์), ข้อมูลอยู่ได้นานเท่าไรก่อนรีเฟรช, และเหตุการณ์ใดต้องทำให้แคชหมดอายุ
ลองนึกถึง flow ทั่วไป: ผู้ใช้เปิดคำสั่งซื้อ แล้วกลับไปที่รายการคำสั่งซื้อ ถ้ารายการมาจากแคช มันอาจยังแสดงสถานะเก่า ถ้าคุณรีเฟรชทุกครั้ง รายการอาจกระพริบและรู้สึกช้า กฎชัดเจนเช่น “แสดงแคชทันที, รีเฟรชเบื้องหลัง, และอัพเดตทั้งสองหน้าจอเมื่อได้คำตอบ” ทำให้ประสบการณ์สอดคล้องทั้งการนำทาง
แคชไม่ใช่แค่ "ข้อมูลที่เก็บไว้" มันคือสำเนาที่เก็บพร้อมกฎว่าสำเนานั้นยังใช้ได้หรือไม่ ถ้าคุณเก็บเพย์โหลดแต่ไม่เก็บกฎ คุณจะได้สองความเป็นจริง: หนึ่งหน้าจอแสดงข้อมูลใหม่ อีกหน้าจอแสดงของเมื่อวาน
โมเดลปฏิบัติได้คือใส่รายการแคชทุกชิ้นลงในหนึ่งในสามสถานะ:
กรอบนี้ทำให้ UI ทำนายได้เพราะมันตอบสนองแบบเดียวกันทุกครั้งที่เห็นสถานะนั้น
กฎความสดควรตั้งบนสัญญาณที่อธิบายให้เพื่อนร่วมทีมเข้าใจได้ ตัวเลือกทั่วไปคือหมดอายุแบบเวลา (เช่น 5 นาที), การเปลี่ยนเวอร์ชัน (schema หรือเวอร์ชันแอป), การกระทำของผู้ใช้ (pull to refresh, submit, delete), หรือคำใบ้จากเซิร์ฟเวอร์ (ETag, last-updated, หรือ explicit “cache invalid”)
ตัวอย่าง: หน้าจอโปรไฟล์โหลดข้อมูลผู้ใช้จากแคชทันที ถ้ามันเป็นล้าสมัยแต่ใช้งานได้ จะแสดงชื่อและอวาตาร์จากแคชแล้วค่อยๆ รีเฟรช หากผู้ใช้เพิ่งแก้โปรไฟล์ นั่นคือช่วงที่ต้องรีเฟรช แอปควรอัพเดตแคชทันทีเพื่อให้ทุกหน้าจอสอดคล้อง
ตัดสินใจว่าใครเป็นเจ้าของกฎพวกนี้ โดยปกติค่าดีที่สุดคือ: ชั้นข้อมูลเป็นเจ้าของความสดและการ invalidation ส่วน UI แค่ตอบสนอง (แสดงแคช, แสดงโหลด, แสดงข้อผิดพลาด) และ backend ให้คำใบ้เมื่อทำได้ วิธีนี้ป้องกันไม่ให้แต่ละหน้าจอคิดกฎของตัวเอง
การแคชที่ดีเริ่มจากคำถามเดียว: ถ้าข้อมูลนี้เก่าไปเล็กน้อย จะทำร้ายผู้ใช้ไหม? ถ้าคำตอบคือ “น่าจะยังได้” นั่นมักเหมาะกับแคชท้องถิ่น
ข้อมูลที่อ่านบ่อยและเปลี่ยนช้า มักคุ้มค่ากับการแคช: ฟีดและรายการที่คนเลื่อนบ่อย, เนื้อหาแบบแคตาล็อก (สินค้า บทความ เทมเพลต), และข้อมูลอ้างอิงเช่นหมวดหมู่หรือประเทศ ตั้งค่าและการตั้งค่าผู้ใช้ก็อยู่ในกลุ่มนี้ รวมถึงข้อมูลโปรไฟล์พื้นฐานเช่นชื่อและ URL อวาตาร์
ด้านเสี่ยงคือสิ่งที่เกี่ยวกับเงินหรือเวลาจริง: ยอดเงิน, สถานะการชำระเงิน, ความพร้อมของสต็อก, ช่องเวลานัดหมาย, เวลาจัดส่งโดยประมาณ, และ “เห็นล่าสุดออนไลน์” อาจก่อปัญหาจริงหากล้าสมัย คุณยังสามารถแคชเพื่อความเร็ว แต่ต้องถือว่าแคชเป็นที่ว่างชั่วคราวและบังคับรีเฟรชที่จุดตัดสินใจ (เช่น ก่อนยืนยันคำสั่งซื้อ)
สถานะ UI ที่ได้มาจากการคำนวณเองเป็นอีกประเภทหนึ่ง การบันทึกแท็บที่เลือก ตัวกรอง คิวรีการค้นหา เรียงลำดับ หรือสถานะการเลื่อน ช่วยให้การนำทางลื่นไหล แต่ก็อาจทำให้ผู้ใช้สับสนเมื่อการตั้งค่าเก่าปรากฏขึ้นโดยไม่ตั้งใจ กฎง่ายๆ ที่ได้ผล: เก็บสถานะ UI ในหน่วยความจำขณะที่ผู้ใช้ยังอยู่ใน flow นั้น แต่รีเซ็ตเมื่อผู้ใช้ตั้งใจ "เริ่มใหม่" (เช่น กลับไปหน้าหลัก)
หลีกเลี่ยงการแคชที่เสี่ยงด้านความปลอดภัยหรือความเป็นส่วนตัว: ความลับ (รหัสผ่าน, API keys), โทเค็นใช้ครั้งเดียว (OTP, รหัสรีเซ็ตรหัสผ่าน), และข้อมูลส่วนบุคคลที่ละเอียดอ่อน เว้นแต่ว่าคุณต้องการการเข้าถึงแบบออฟไลน์จริงๆ อย่าแคชข้อมูลบัตรเต็มหรือสิ่งที่เพิ่มความเสี่ยงการฉ้อโกง
ในแอปช็อปปิ้ง การแคชรายการสินค้าเป็นประโยชน์มาก แต่หน้าชำระเงินควรรีเฟรชยอดและความพร้อมสินค้าเสมอก่อนการชำระ
แอป Flutter ส่วนใหญ่ต้องการแคชท้องถิ่นเพื่อให้หน้าจอโหลดเร็วและไม่กระพริบขณะเครือข่ายตื่น เตือนใจว่าตัดสินใจที่สำคัญคือตำแหน่งที่เก็บข้อมูล เพราะแต่ละชั้นมีความเร็ว ขนาด และพฤติกรรมการล้างต่างกัน
แคชในหน่วยความจำเร็วที่สุด เหมาะสำหรับข้อมูลที่เพิ่งดึงและจะนำกลับมาใช้ในขณะที่แอปเปิด เช่น โปรไฟล์ปัจจุบัน ผลการค้นหาล่าสุด หรือสินค้าที่เพิ่งดู ข้อแลกเปลี่ยน: มันหายไปเมื่อแอปถูกฆ่า จึงช่วยไม่ได้ตอน cold start หรือการใช้งานแบบออฟไลน์
การเก็บเป็น key-value บนดิสก์เหมาะกับไอเท็มเล็กๆ ที่ต้องการข้ามการรีสตาร์ท คิดถึงการตั้งค่าและบล็อบเล็กๆ: feature flags, "แท็บที่เลือกล่าสุด", และ JSON เล็กๆ ที่เปลี่ยนไม่บ่อย เก็บให้เล็กโดยเจตนา เมื่อคุณเริ่มใส่รายการใหญ่ลงไป การอัพเดตจะยุ่งยากและจะบวมง่าย
ฐานข้อมูลท้องถิ่นดีที่สุดเมื่อข้อมูลของคุณใหญ่ มีโครงสร้าง หรือต้องการพฤติกรรมออฟไลน์ นอกจากนี้ยังช่วยเมื่อคุณต้องการการสืบค้น ("ข้อความที่ยังไม่ได้อ่านทั้งหมด", "ของในตะกร้า", "คำสั่งซื้อเดือนที่ผ่านมา") แทนที่จะโหลด blob ใหญ่แล้วกรองในหน่วยความจำ
เพื่อให้การแคชทำนายได้ ให้เลือกที่เก็บหลักหนึ่งชั้นต่อชนิดข้อมูลและหลีกเลี่ยงการเก็บชุดข้อมูลเดียวกันในสามที่
กฎง่ายๆ:
วางแผนเรื่องขนาดด้วย กำหนดว่า “ใหญ่เกินไป” คืออะไร เก็บได้นานเท่าไร และล้างอย่างไร เช่น จำกัดผลการค้นหาที่แคชไว้ล่าสุด 20 คำค้น และลบเรคคอร์ดเก่ากว่า 30 วันเป็นประจำ
กฎรีเฟรชควรง่ายพอที่คุณอธิบายเป็นประโยคเดียวต่อหน้าจอ นั่นคือที่ที่การแคชสมเหตุผล: ผู้ใช้ได้หน้าจอเร็ว แอปยังเชื่อถือได้
กฎที่ง่ายที่สุดคือ TTL (time to live) เก็บข้อมูลพร้อม timestamp และถือว่ามันสดเป็นเวลา เช่น 5 นาที หลังจากนั้นจะล้าสมัย TTL เหมาะกับข้อมูล "nice to have" เช่น ฟีด หมวดหมู่ หรือคำแนะนำ
การแยก TTL เป็น soft TTL และ hard TTL ช่วยได้
ด้วย soft TTL คุณแสดงแคชทันที แล้วรีเฟรชเบื้องหลังและอัพเดต UI ถ้ามีการเปลี่ยนแปลง ด้วย hard TTL คุณหยุดแสดงข้อมูลเก่าทันทีเมื่อหมดอายุ คุณจะบล็อกด้วยตัวโหลดหรือแสดงสถานะ "ออฟไลน์/ลองอีกครั้ง" Hard TTL เหมาะกับกรณีที่การผิดพลาดแย่กว่าการช้า เช่น ยอดคงเหลือ สถานะคำสั่งซื้อ หรือสิทธิ์การเข้าถึง
ถ้า backend รองรับ ให้เลือกวิธี "รีเฟรชเฉพาะเมื่อเปลี่ยน" โดยใช้ ETag, updatedAt หรือฟิลด์เวอร์ชัน แอปจะถามว่า "เปลี่ยนไหม?" และข้ามการดาวน์โหลดเพย์โหลดเต็มเมื่อไม่มีอะไรใหม่
ค่าดีฟอลต์ที่เป็นมิตรกับผู้ใช้คือ stale-while-revalidate: แสดงทันที รีเฟรชเงียบๆ แล้ววาดใหม่เฉพาะเมื่อผลต่าง มันให้ความเร็วโดยไม่ทำให้กระพริบสะดุด
ความสดต่อหน้าจอมักจะออกมาแบบนี้:
เลือกกฎตามต้นทุนของการผิดพลาด ไม่ใช่แค่ต้นทุนการดึงข้อมูล
การ invalidation เริ่มจากคำถามเดียว: เหตุการณ์ไหนทำให้ข้อมูลแคชไม่น่าเชื่อถือกว่าการดึงใหม่? ถ้าคุณเลือกชุดทริกเกอร์เล็กๆ แล้วยึดตามมัน พฤติกรรมจะทำนายได้และ UI จะนิ่ง
ทริกเกอร์ที่สำคัญในแอปจริง:
ตัวอย่าง: ผู้ใช้แก้ไขรูปโปรไฟล์แล้วกลับ หากคุณพึ่งพาการรีเฟชตามเวลาเท่านั้น หน้าจอก่อนหน้าอาจยังแสดงรูปเก้าจนกว่าจะมีการดึงครั้งถัดไป แทนที่จะเป็นแบบนั้น ให้ถือว่าการแก้ไขเป็นทริกเกอร์: อัพเดตอ็อบเจ็กต์โปรไฟล์ในแคชทันทีและทำเครื่องหมายว่ามันสดด้วย timestamp ใหม่
เก็บกฎ invalidation ให้เล็กและชัดเจน ถ้าคุณชี้ไม่ถูกว่าเหตุการณ์ไหนเป็นตัวทำให้แคชหมดอายุ คุณจะรีเฟรชบ่อยเกินไป (UI ช้า กระพริบ) หรือไม่พอ (หน้าจอเก่า)
เริ่มจากการระบุหน้าจอสำคัญและข้อมูลที่แต่ละหน้าต้องการ อย่าคิดเป็น endpoints ให้คิดเป็นวัตถุที่ผู้ใช้มองเห็น: โปรไฟล์, ตะกร้า, รายการคำสั่งซื้อ, รายการสินค้า, จำนวนที่ยังไม่ได้อ่าน
จากนั้นเลือกแหล่งความจริงเดียวต่อชนิดข้อมูล ใน Flutter ปกติคือ repository ที่ซ่อนว่าข้อมูลมาจากไหน (memory, disk, network) หน้าจอไม่ควรตัดสินใจเมื่อใดจะเรียกเครือข่าย เขาควรขอข้อมูลจาก repository แล้วตอบสนองต่อสถานะที่คืนมา
โฟลว์ปฏิบัติได้:
เมตาดาต้าคือสิ่งที่ทำให้กฎบังคับใช้ได้ ถ้า ownerUserId เปลี่ยน (logout/login) คุณสามารถทิ้งหรือเพิกเฉยเรคคอร์ดแคชเก่าแทนที่จะโชว์ข้อมูลผู้ใช้ก่อนหน้าเป็นเสี้ยววินาที
สำหรับพฤติกรรม UI ให้ตัดสินใจล่วงหน้าว่า "ล้าสมัย" หมายถึงอะไร กฎที่พบบ่อย: แสดงข้อมูลล้าสมัยทันทีเพื่อไม่ให้หน้าจอว่าง เริ่มรีเฟรชเบื้องหลัง แล้วอัพเดตเมื่อได้ข้อมูลใหม่ ถ้ารีเฟรชล้มเหลว ให้เก็บข้อมูลล้าสมัยไว้และแสดงข้อผิดพลาดเล็กๆ ชัดเจน
จากนั้นล็อกกฎเหล่านี้ด้วยการทดสอบเบื้องต้น:
นั่นคือความต่างระหว่าง "เรามีแคช" กับ "แอปของเราทำงานเหมือนกันทุกครั้ง"
ไม่มีอะไรทำลายความเชื่อถือเร็วเท่าการเห็นค่าหนึ่งบนหน้ารายการ แตะเข้าไปแก้แล้วกลับมาเจอค่าตัวเดิมอีกครั้ง ความสอดคล้องข้ามการนำทางมาจากการทำให้หน้าจอทุกหน้าตัวอ่านจากแหล่งเดียว
กฎที่มั่นคง: ดึงครั้งเดียว เก็บครั้งเดียว แสดงหลายครั้ง อย่าให้หน้าจอเรียก endpoint เดียวกันอย่างอิสระแล้วเก็บสำเนาส่วนตัว ใส่ข้อมูลแคชไว้ในสโตร์ที่แชร์ (ชั้นจัดการสถานะของคุณ) และให้ทั้งรายการและหน้ารายละเอียดเฝ้าดูข้อมูลเดียวกัน
เก็บที่เดียวที่เป็นเจ้าของค่าปัจจุบันและความสด หน้าจอสามารถขอรีเฟรชได้ แต่ไม่ควรจัดการตัวจับเวลา retry และการแยกพาร์สเอง
นิสัยปฏิบัติที่ป้องกัน "สองความจริง":
แม้มีการตั้งกฎดี ผู้ใช้ยังอาจเห็นข้อมูลล้าสมัย (ออฟไลน์ เน็ตช้า แอปถูกแบ็กกราวด์) ทำให้ชัดด้วยสัญญาณเล็กๆ เช่น ตราเวลา “อัพเดตเมื่อครู่ที่แล้ว”, ตัวบอกสถานะ "กำลังรีเฟรช...", หรือป้าย "ออฟไลน์"
สำหรับการแก้ไข การอัพเดตเชิงมโนมติ (optimistic update) มักให้ความรู้สึกดีที่สุด ตัวอย่าง: ผู้ใช้เปลี่ยนราคาสินค้าในหน้ารายละเอียด ให้ปรับสตอร์ที่แชร์ทันทีเพื่อให้รายการแสดงราคาใหม่เมื่อย้อนกลับ หากบันทึกล้มเหลว ให้ย้อนกลับเป็นค่าก่อนหน้าและแสดงข้อผิดพลาดสั้นๆ
ความล้มเหลวส่วนใหญ่ของการแคชน่าเบื่อ: แคชทำงาน แต่ไม่มีใครอธิบายได้เมื่อควรใช้ มันหมดอายุเมื่อไร และใครเป็นเจ้าของ
กับดักแรกคือแคชโดยไม่มีเมตาดาต้า หากคุณเก็บแค่เพย์โหลด คุณบอกไม่ได้ว่ามันเก่าเท่าไร แอปเวอร์ชันใดสร้างมัน หรือเป็นของผู้ใช้ใด เก็บ savedAt อย่างน้อย หมายเลขเวอร์ชัน และ userId นิสัยเดียวนี้ป้องกันบั๊ก "ทำไมหน้าจอนี้ผิด" ได้มาก
ปัญหาทั่วไปอีกอย่างคือมีแคชหลายที่สำหรับข้อมูลเดียวโดยไม่มีเจ้าของ หน้ารายการเก็บลิสต์ในหน่วยความจำ repository เขียนลงดิสก์ และหน้ารายละเอียดดึงอีกครั้งและเก็บที่อื่น เลือกแหล่งความจริงหนึ่งที่ชัดเจน (มักเป็นเลเยอร์ repository) แล้วให้ทุกหน้าจออ่านผ่านมัน
การเปลี่ยนบัญชีเป็นกับดักบ่อยมาก หากคนล็อกเอาต์หรือสลับบัญชี ให้ล้างตารางและคีย์ที่ผูกกับผู้ใช้ มิฉะนั้นคุณอาจแสดงรูปโปรไฟล์หรือคำสั่งซื้อของผู้ใช้ก่อนหน้าเป็นเสี้ยววินาที ซึ่งรู้สึกเหมือนการละเมิดความเป็นส่วนตัว
แก้ปัญหาเชิงปฏิบัติที่ครอบคลุมด้านบน:
ตัวอย่าง: รายการสินค้าโหลดทันทีจากแคชแล้วรีเฟรชเงียบๆ หากรีเฟรชล้มเหลว ให้ยังคงแสดงข้อมูลแคชแต่บอกว่ามันอาจล้าสมัยและเสนอ Retry อย่าบล็อก UI ขณะรีเฟรชเมื่อข้อมูลแคชยังพอใช้ได้
ก่อนปล่อย ให้เปลี่ยนการแคชจาก "ดูเหมือนใช้ได้" เป็นกฎที่ทดสอบได้ ผู้ใช้ควรเห็นข้อมูลที่สมเหตุสมผลแม้นำทางถอยหลัง ไปออฟไลน์ หรือลงชื่อเข้าด้วยบัญชีอื่น
สำหรับทุกหน้าจอ ให้ตัดสินใจว่าข้อมูลจะถือว่ามันสดนานแค่ไหน อาจเป็นนาทีสำหรับข้อมูลเคลื่อนไหวเร็ว (ข้อความ ยอดเงิน) หรือชั่วโมงสำหรับข้อมูลเปลี่ยนช้า (การตั้งค่า หมวดหมู่สินค้า) แล้วยืนยันว่าจะเกิดอะไรขึ้นเมื่อมันไม่สด: รีเฟรชเบื้องหลัง, รีเฟรชเมื่อเปิด, หรือ pull-to-refresh แบบแมนนวล
สำหรับแต่ละชนิดข้อมูล ให้ตัดสินใจว่าเหตุการณ์ใดต้องล้างหรือข้ามแคช ทริกเกอร์ทั่วไปได้แก่ logout, แก้ไขไอเท็ม, สลับบัญชี, และอัพเดตแอปที่เปลี่ยนรูปแบบข้อมูล เก็บเมตาดาต้าเล็กๆ ข้างเพย์โหลด:
ชัดเจนเรื่องความเป็นเจ้าของ: ใช้ repository หนึ่งตัวต่อชนิดข้อมูล (เช่น ProductsRepository) ไม่ใช่ต่อ widget Widgets ควรขอข้อมูล ไม่ใช่ตัดสินใจกฎแคช
นอกจากนี้ตัดสินใจและทดสอบพฤติกรรมออฟไลน์ ยืนยันว่าหน้าจอไหนแสดงจากแคช การทำงานใดถูกปิด และข้อความที่แสดง ("กำลังแสดงข้อมูลที่บันทึกไว้" พร้อมคอนโทรลรีเฟรชที่มองเห็นได้) ควรมีการรีเฟรชด้วยมือบนทุกหน้าจอที่พึ่งพาแคชและหาได้ง่าย
นึกภาพแอปช็อปง่ายๆ ที่มีสามหน้าจอ: แคตาล็อก (รายการ), รายละเอียดสินค้า, และแท็บ Favorites ผู้ใช้เลื่อนแคตาล็อก เปิดสินค้า และแตะไอคอนหัวใจเพื่อเก็บเป็นรายการโปรด เป้าหมายคือรู้สึกเร็วแม้เครือข่ายช้า โดยไม่แสดงค่าที่สับสน
แคชในเครื่องสิ่งที่ช่วยให้เรนเดอร์ทันที: หน้าคาตาล็อก (ID, ชื่อ, ราคา, URL ภาพย่อ, ธงโปรด), รายละเอียดสินค้า (คำอธิบาย, สเปก, ความพร้อม, lastUpdated), เมตาดาต้าภาพ (URL, ขนาด, cache keys), และรายการโปรดของผู้ใช้ (ชุด ID สินค้า พร้อม timestamp ทางเลือก)
เมื่อผู้ใช้เปิดแคตาล็อก ให้แสดงผลจากแคชทันที แล้วตรวจสอบความถูกต้องเบื้องหลัง ถ้ามีข้อมูลสดมาใหม่ ให้อัพเดตเฉพาะส่วนที่เปลี่ยนและรักษาตำแหน่งการเลื่อนให้คงที่
สำหรับการสลับโปรด ให้ถือว่าเป็นการกระทำที่ต้องสอดคล้อง อัพเดตชุดโปรดในเครื่องทันที (optimistic update) แล้วอัพเดตแถวสินค้าและรายละเอียดสินค้าในแคชสำหรับ ID นั้น ถ้าเรียกเครือข่ายล้มเหลว ให้ย้อนกลับและโชว์ข้อความสั้นๆ
เพื่อให้การนำทางสอดคล้อง ให้ขับเคลื่อนทั้งไอคอนหัวใจในรายการและรายละเอียดจากแหล่งความจริงเดียว (แคชท้องถิ่นหรือสโตร์) ไม่ใช่จากสถานะหน้าจอแยกต่างหาก หัวใจบนรายการจะอัพเดตทันทีเมื่อย้อนกลับจากรายละเอียด หน้ารายละเอียดสะท้อนการเปลี่ยนแปลงจากรายการ และจำนวนในแท็บ Favorites ตรงกันโดยไม่ต้องรอรีเฟช
เพิ่มกฎรีเฟรชง่ายๆ: แคชแคตาล็อกหมดอายุเร็ว (นาที), รายละเอียดสินค้ายาวขึ้นหน่อย, และรายการโปรดไม่หมดอายุแต่จะประสานหลังการล็อกอิน/ล็อกเอาต์
การแคชจะไม่เป็นเรื่องลึกลับเมื่อทีมชี้ไปยังหน้ากฎเดียวและตกลงกันว่าควรเกิดอะไรขึ้น เป้าหมายไม่ใช่ความสมบูรณ์แบบ แต่เป็นพฤติกรรมที่ทำนายได้และคงที่ข้าม release
เขียนตารางเล็กๆ ต่อหน้าจอและทำให้กระชับพอให้ทบทวนได้ขณะเปลี่ยนแปลง: ชื่อหน้าจอและข้อมูลหลัก, ที่เก็บและคีย์, กฎความสด (TTL, เหตุการณ์, หรือแมนนวล), ทริกเกอร์ invalidation, และสิ่งที่ผู้เห็นระหว่างรีเฟรช
เพิ่มการล็อกน้ำหนักเบาในช่วง tuning บันทึกการโดนแคช, การพลาด, และเหตุผลที่ทำให้เกิดการรีเฟรช (TTL หมด, ผู้ใช้ดึง, แอป resume, mutation เสร็จ) เมื่อลูกค้ารายงานว่า “รายการนี้รู้สึกผิด” โลกเหล่านี้ทำให้บั๊กแก้ไขได้
เริ่มจาก TTL ง่ายๆ แล้วปรับตามที่ผู้ใช้สังเกต ฟีดข่าวอาจยอมรับความล้าสมัย 5–10 นาที ขณะที่หน้าสถานะคำสั่งซื้ออาจต้องรีเฟรชเมื่อ resume และหลังการชำระเงิน
ถ้าคุณกำลังสร้างแอป Flutter แบบเร่งด่วน การร่าง data layer และกฎแคชก่อนจะช่วยมาก สำหรับทีมที่ใช้ Koder.ai (koder.ai) โหมดวางแผนเป็นที่ที่สะดวกในการเขียนกฎต่อหน้าจอเหล่านี้ก่อน แล้วค่อยสร้างให้ตรงตามมัน
เมื่อคุณปรับพฤติกรรมการรีเฟรช ให้ปกป้องหน้าจอที่เสถียรขณะทดลอง สแนปช็อตและการย้อนกลับช่วยประหยัดเวลาเมื่อตั้งกฎใหม่ทำให้เกิดกระพริบ หน้าจอว่าง หรือค่าจำนวนไม่ตรงกัน
เริ่มจากกฎเดียวที่ชัดเจนต่อหน้าจอ: แสดงอะไรได้ทันที (จากแคช), เมื่อใดต้องรีเฟรช, และผู้ใช้เห็นอะไรขณะรีเฟรช ถ้าคุณอธิบายกฎนั้นไม่ได้นอกจากประโยคเดียว แอปจะรู้สึกไม่สอดคล้องในท้ายที่สุด
มองแคชเป็นข้อมูลที่มีสถานะความสด ถ้าเป็น สด ให้แสดง ถ้าเป็น ล้าสมัยแต่ใช้งานได้ ให้แสดงตอนนี้และรีเฟรชเงียบๆ ถ้าเป็น ต้องรีเฟรช ให้ดึงข้อมูลก่อนแสดง (หรือโชว์สถานะโหลด/ออฟไลน์) วิธีนี้ทำให้พฤติกรรม UI สม่ำเสมอ แทนที่จะเป็นแบบ "บางครั้งอัพเดต บางครั้งไม่"
แคชข้อมูลที่อ่านบ่อยและยอมให้เก่าบ้างได้โดยไม่ทำร้ายผู้ใช้ เช่น ฟีด, แคตาล็อก, ข้อมูลอ้างอิง และข้อมูลโปรไฟล์พื้นฐาน ระวังข้อมูลที่เกี่ยวกับเงินหรือเวลาจริง เช่น ยอดคงเหลือ สถานะการชำระเงิน สต็อก หรือเวลาจัดส่ง—คุณอาจแคชเพื่อความเร็วแต่ต้องรีเฟรชก่อนจุดตัดสินใจ เช่น ก่อนยืนยันคำสั่งซื้อ
ใช้หน่วยความจำ (memory) สำหรับการนำกลับมาใช้ทันทีในเซสชันปัจจุบัน เช่น โปรไฟล์ปัจจุบันหรือรายการที่เพิ่งดู ใช้ key-value บนดิสก์สำหรับรายการเล็ก ๆ ที่ต้องการอยู่ข้ามการรีสตาร์ท เช่น การตั้งค่า ใช้ฐานข้อมูลท้องถิ่นเมื่อข้อมูลใหญ่ มีโครงสร้าง หรือต้องการการสืบค้นหรือการทำงานแบบออฟไลน์ เช่น ข้อความ คำสั่งซื้อ หรือสต็อก
TTL เป็นค่าเริ่มต้นที่ดี: ถือว่าข้อมูลสดในช่วงเวลาหนึ่ง แล้วจึงรีเฟรช แต่ประสบการณ์ที่ดีกว่าคือ “แสดงจากแคชทันที, รีเฟรชพื้นหลัง, แล้วอัพเดตถ้าข้อมูลเปลี่ยน” เพราะจะหลีกเลี่ยงหน้าจอว่างและการกระพริบ
ทริกเกอร์ที่ควรใช้คือเหตุการณ์ที่ทำให้ความน่าเชื่อถือของแคชลดลงจนคุ้มค่าที่จะดึงใหม่: การแก้ไขโดยผู้ใช้ (create/update/delete), การล็อกอิน/ล็อกเอาต์หรือสลับบัญชี, การกลับมาจากแบ็กกราวด์หากข้อมูลเก่ากว่า TTL, และการรีเฟรชโดยผู้ใช้ อย่าใส่ทริกเกอร์เยอะเกินไปจนรีเฟรชตลอดเวลา หรือโลว์เกินไปจนไม่รีเฟรชเมื่อสำคัญ
ให้ทั้งสองหน้าจออ่านจากแหล่งข้อมูลเดียวกัน ไม่ใช่สำเนาส่วนตัว เมื่อผู้ใช้แก้ไขบนหน้ารายละเอียด ให้ปรับอ็อบเจ็กต์ที่แคชร่วมกันทันทีเพื่อให้รายการแสดงค่าใหม่เมื่อย้อนกลับ แล้วซิงค์กับเซิร์ฟเวอร์และย้อนกลับเฉพาะเมื่อเซฟล้มเหลว
บันทึกเมตาดาต้าคู่กับเพย์โหลด โดยเฉพาะ timestamp และตัวระบุผู้ใช้ เมื่อล็อกเอาต์หรือสลับบัญชี ให้ลบหรือแยกแคชที่ผูกกับผู้ใช้เดิมทันทีและยกเลิกคำขอที่ยังรันอยู่เพื่อหลีกเลี่ยงการแสดงข้อมูลของผู้ใช้ก่อนหน้าเป็นวินาทีสั้นๆ
โดยทั่วไป ให้คงแสดงข้อมูลล้าสมัยไว้และแสดงข้อผิดพลาดเล็กๆ ที่ชัดเจนพร้อมปุ่ม Retry แทนที่จะทำให้หน้าจอว่าง หากหน้าจอไม่สามารถแสดงข้อมูลเก่าได้อย่างปลอดภัย ให้เปลี่ยนเป็นกฎ must-refresh และโชว์สถานะโหลดหรือข้อความออฟไลน์แทนการแสร้งว่าค่าเก่าเชื่อถือได้
เก็บกฎแคชไว้ใน data layer (เช่น repositories) เพื่อให้ทุกหน้าจอปฏิบัติตามพฤติกรรมเดียวกัน เขียนกฎต่อหน้าจอในโหมดวางแผนก่อนแล้วจึง implement UI ให้แค่ตอบสนองต่อสถานะ แทนที่จะให้ widget แต่ละตัวตัดสินใจเอง