Hot Reload — เทคโนโลยีที่ช่วยให้อัปเดตโค้ดของแอปพลิเคชันมือถือที่กำลังทำงานอยู่ได้โดยไม่ต้องรีสตาร์ทและไม่สูญเสียสถานะปัจจุบัน นักพัฒนาแก้ไขซอร์สโค้ด — ภายในไม่กี่วินาทีการเปลี่ยนแปลงจะปรากฏบนหน้าจออุปกรณ์หรืออีมูเลเตอร์ นี่คือคุณสมบัติหลักของ Flutter และ React Native ที่ช่วยเร่งการพัฒนาแบบรอบซ้ำได้อย่างมาก: เวลาของรอบแก้ไข-ดูลดลงจาก 5–10 วินาที (สร้างใหม่) เหลือ 300–500 มิลลิวินาที ตาม Flutter Documentation, 2025, hot reload ทำการคอมไพล์โค้ดที่เปลี่ยนแปลงแบบเพิ่มหน่วยและส่งการอัปเดตไปยัง Dart VM
ประเด็นสำคัญ
Hot Reload เป็นกลไกการพัฒนาที่ซอร์สโค้ดถูกแก้ไขและนำไปใช้กับแอปพลิเคชันที่กำลังทำงานอยู่โดยไม่ต้องหยุดมัน นักพัฒนาแก้ไขไฟล์ บันทึก และภายใน 0.3–2 วินาทีอินเทอร์เฟซที่อัปเดตจะปรากฏบนหน้าจอ สถานะของแอปพลิเคชัน (ตัวนับ ตำแหน่งเลื่อน ข้อมูลที่ป้อน) จะถูกเก็บไว้ — นักพัฒนาไม่สูญเสียบริบท
แนวคิดของ hot reload เกิดขึ้นในเครื่องมือเว็บยุคแรก (LiveReload, 2010) และถูกปรับใช้สำหรับการพัฒนามือถือโดย Flutter (2017) และ React Native (2015) ปัจจุบัน hot reload เป็นคุณสมบัติบังคับของเฟรมเวิร์กมือถือสมัยใหม่ พร้อมกับ การกำหนดค่าดีบัก และการทำโปรไฟล์ หากไม่มี hot reload การพัฒนา UI ถือว่าไม่มีประสิทธิภาพ: การตรวจสอบการเปลี่ยนแปลงแต่ละครั้งต้องใช้เวลา 10–30 วินาทีในการสร้างใหม่และเริ่มต้น
ในทางเทคนิค hot reload ประกอบด้วยสามขั้นตอน: การตรวจจับการเปลี่ยนแปลง (ตัวเฝ้าดูไฟล์), การคอมไพล์โค้ดที่เปลี่ยนแปลง (คอมไพเลอร์แบบเพิ่มหน่วย), และการนำไปใช้ (การแพตช์แบบร้อน) แต่ละเฟรมเวิร์กนำขั้นตอนเหล่านี้ไปใช้แตกต่างกัน แต่ผลลัพธ์เหมือนกัน: ความล่าช้าน้อยที่สุดระหว่างการแก้ไขและการแสดงผล
Hot Reload ใน Flutter สร้างขึ้นบนสถาปัตยกรรม Dart VM และการคอมไพล์แบบ JIT เมื่อนักพัฒนากด “Hot Reload” ใน IDE หรือบันทึกไฟล์ Flutter จะทำการคอมไพล์ไลบรารี Dart ที่เปลี่ยนแปลงแบบเพิ่มหน่วยเป็นไฟล์เคอร์เนล (.dill) Dart VM โหลดไฟล์เหล่านี้และแทนที่การทำงานของฟังก์ชันที่เปลี่ยนแปลงในแอปพลิเคชันที่กำลังทำงานอยู่
// วิดเจ็ต Flutter ที่มีสถานะถูกเก็บรักษาระหว่าง hot reload
class CounterWidget extends StatefulWidget {
@override
State createState() => _CounterState();
}
class _CounterState extends State {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('ตัวนับ: $_counter'),
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: Text('เพิ่ม'),
),
],
);
}
}
ในตัวอย่าง StatefulWidget CounterWidget จะรักษาฟิลด์ _counter ไว้ระหว่าง hot reload Dart VM สร้างสถานะ (State) ใหม่โดยเรียก reassemble() แต่ไม่ได้รีเซ็ต _counter — ค่าจะถูกเก็บไว้เว้นแต่วิดเจ็ตจะถูกสร้างใหม่ทั้งหมด Flutter เรียก reassemble() สำหรับออบเจ็กต์ State ทั้งหมด และ build() จะทำงานอีกครั้งด้วยโค้ดที่อัปเดตและสถานะที่เก็บไว้
เมื่อใดที่ hot reload ไม่ทำงาน: หากตัวแปรเริ่มต้นแบบสแตติก (static const), ตัวแปรโกลบอล, main(), การประกาศคลาส enum/mixin หรือโค้ดใน @override initState() ถูกเปลี่ยนแปลง ในกรณีเหล่านี้จำเป็นต้องใช้ Hot Restart ตามข้อมูลของ Flutter Team (2025), hot reload ประสบความสำเร็จใน 85–90% ของกรณี; 10–15% ของการเปลี่ยนแปลงต้องการการรีสตาร์ทเต็มรูปแบบ
Dart VM ในโหมดดีบักทำงานเป็นคอมไพเลอร์ JIT: มันแปลความหมายโค้ด Dart ผ่านรูปแบบเคอร์เนล (คล้ายกับไบต์โค้ด) Hot reload โหลดไฟล์เคอร์เนลใหม่และแทนที่คำจำกัดความของฟังก์ชันเก่า VM ไม่ได้รีสตาร์ทไอโซเลต — การดำเนินการแบบอะซิงโครนัสทั้งหมด (Future, Stream) ยังคงทำงานต่อไป ในโหมดรีลีส Dart จะถูกคอมไพล์แบบ AOT (dart2native) และ hot reload ไม่สามารถใช้งานได้
Fast Refresh (เดิมชื่อ Hot Reloading) ใน React Native ใช้ Metro bundler — ตัวรวมโมดูล JavaScript ที่ติดตามการเปลี่ยนแปลงไฟล์ เมื่อนักพัฒนาบันทึกไฟล์ Metro จะคอมไพล์เฉพาะโมดูลที่เปลี่ยนแปลง (HMR — Hot Module Replacement) และส่งการอัปเดตผ่าน WebSocket ไปยังแอปพลิเคชันที่กำลังทำงานอยู่
// คอมโพเนนต์ React Native ที่รักษาสถานะระหว่าง hot reload
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<View>
<Text>ตัวนับ: {count}Text>
<Button title="เพิ่ม"
onPress={() => setCount(c => c + 1)} />
View>
);
};
Fast Refresh รักษาสถานะ React (useState, useReducer) ไว้เมื่ออัปเดตโมดูล Metro HMR ส่งเฉพาะส่วนต่างของโมดูลที่เปลี่ยนแปลง — ไม่ใช่ทั้งบันเดิล React Native ใช้ React Fast Refresh ซึ่งพัฒนาโดยทีม React (Dan Abramov, 2019): มันสร้างเรนเดอร์ใหม่สำหรับคอมโพเนนต์แต่รักษาสถานะของ hook และ props ไว้หากลายเซ็นของคอมโพเนนต์ไม่เปลี่ยนแปลง
Fast Refresh ไม่ทำงานเมื่อเปลี่ยนแปลง: การส่งออกคอมโพเนนต์, hooks (useEffect, useMemo), การพึ่งพาโมดูล และโมดูลเนทีฟ (Java/Objective-C) การเปลี่ยนแปลงดังกล่าวต้องการ Reload (โหลดบันเดิล JS ใหม่ทั้งหมด) หรือ Rebuild (สร้างโค้ดเนทีฟใหม่) เวลา fast refresh — 200–800 มิลลิวินาที, reload เต็มรูปแบบ — 2–5 วินาที
Hot Reload และ Hot Restart เป็นโหมดอัปเดตโค้ดสองโหมดที่มีสถานการณ์การใช้งานต่างกัน Hot Reload เหมาะสำหรับการเปลี่ยนแปลง UI (สไตล์ เลย์เอาต์ สี ข้อความ) เมื่อโครงสร้างคลาสและประเภทสถานะไม่เปลี่ยนแปลง Hot Restart จำเป็นเมื่อเปลี่ยนลายเซ็นเมธอด เพิ่มวิดเจ็ต/คอมโพเนนต์ใหม่ในทรีราก เปลี่ยนแปลง initState และโมดูลเนทีฟ
| คุณลักษณะ | Hot Reload | Hot Restart |
|---|---|---|
| ความเร็ว | 0.3–2 วินาที | 2–10 วินาที |
| การรักษาสถานะ | ใช่ (ตัวแปร, state, สแต็กการนำทาง) | ไม่ (แอปพลิเคชันเริ่มต้นใหม่) |
| การคอมไพล์ | แบบเพิ่มหน่วย (เฉพาะการเปลี่ยนแปลง) | คอมไพล์ Dart/JS ใหม่ทั้งหมด |
| เมื่อใดควรใช้ | การปรับ UI, สไตล์, ข้อความ, เลย์เอาต์ | การเปลี่ยนแปลงโครงสร้าง, โมดูลใหม่, โค้ดเนทีฟ |
| Flutter | Hot Reload (R) | Hot Restart (Shift + R) |
| React Native | Fast Refresh | Reload (Cmd + R) |
กลยุทธ์ที่แนะนำ: เริ่มต้นด้วย hot reload หากการเปลี่ยนแปลงไม่ถูกนำไปใช้ (IDE แสดง “Reload needed”) — ให้ทำ hot restart ใน Flutter ไอคอนปุ่มจะเปลี่ยน: สายฟ้า (⚡) สำหรับ hot reload, สายฟ้าขีดฆ่าหากจำเป็นต้อง restart ประสิทธิภาพการพัฒนา ด้วย hot reload สูงกว่า 40–60% เมื่อเทียบกับการสร้างใหม่ทั้งหมด (ข้อมูลจาก JetBrains Developer Survey 2024)
การฉีดโค้ด (code injection) เป็นกลไกทั่วไปของ hot reload ที่ใช้โดยเฟรมเวิร์กทั้งหมด ประกอบด้วยสามเฟส เฟสแรก — การตรวจจับการเปลี่ยนแปลง: ตัวเฝ้าดูไฟล์ (ในตัวของ IDE) หรือระบบไฟล์ (FSNotify) ตรวจจับการเปลี่ยนแปลงในไฟล์ .dart, .js, .tsx เฟสที่สอง — การคอมไพล์: คอมไพเลอร์แบบเพิ่มหน่วยแปลงเฉพาะไฟล์ที่เปลี่ยนแปลงเป็นตัวแทนกลาง (kernel .dill สำหรับ Dart, โมดูล HMR สำหรับ JS) เฟสที่สาม — การนำไปใช้: โค้ดใหม่ถูกส่งไปยังอุปกรณ์และแทนที่คำจำกัดความเก่าในหน่วยความจำของแอปพลิเคชันที่กำลังทำงานอยู่
การแทนที่ฟังก์ชันแบบร้อน (hot patching) เป็นเทคนิคที่รันไทม์แทนที่พอยน์เตอร์ฟังก์ชันในตารางเมธอดเสมือน Dart VM ใช้ ClassTable — โครงสร้างภายในที่ประกอบด้วยคลาสที่โหลดทั้งหมด ระหว่าง hot reload VM จะค้นหาคลาสใน ClassTable และแทนที่คำจำกัดความของฟังก์ชันด้วยอันใหม่จากไฟล์เคอร์เนล อินสแตนซ์คลาสที่มีอยู่ทั้งหมดจะได้รับพฤติกรรมใหม่โดยอัตโนมัติ
// Flutter: callback reassemble สำหรับจัดการสถานะหลังจาก hot reload
class MyWidget extends StatefulWidget {
@override
State createState() => _MyState();
}
mixin ReloadAware on State {
@override
void reassemble() {
super.reassemble();
// รีเซ็ตแคชหรือข้อมูลหลังจาก hot reload
clearCache();
}
}
ในตัวอย่าง mixin ReloadAware แทนที่เมธอด reassemble() ซึ่ง Dart VM เรียกบนออบเจ็กต์ State แต่ละตัวหลังจาก hot reload นักพัฒนาสามารถรีเซ็ตแคช เริ่มต้นทรัพยากรใหม่ หรือดำเนินการย้ายสถานะ หากไม่มีเมธอดนี้ ข้อมูลเก่าอาจยังคงอยู่ในแคชและทำให้เกิดความไม่สอดคล้องกันหลังจากการอัปเดตวิดเจ็ต
การแทนที่แบบร้อน ไม่ทำงานสำหรับการเปลี่ยนแปลงที่ต้องการการจัดสรรหน่วยความจำใหม่สำหรับฟิลด์ใหม่ การเปลี่ยนประเภทตัวแปรในคลาส การเพิ่มฟิลด์ใหม่ใน StatefulWidget การเปลี่ยนค่า enum หรือพารามิเตอร์ジェเนอริก การเปลี่ยนแปลงเหล่านี้เข้ากันไม่ได้กับออบเจ็กต์ที่มีอยู่ในหน่วยความจำ — Dart VM ไม่สามารถ “สับเปลี่ยน” ฟิลด์ในออบเจ็กต์ที่ถูกจัดสรรไว้แล้ว สำหรับกรณีเช่นนี้ จำเป็นต้องใช้ hot restart หรือการสร้างใหม่ทั้งหมด
การพัฒนา Android และ iOS แบบเนทีฟตามธรรมเนียมแล้วไม่มี hot reload ที่สมบูรณ์ Android Studio กับ Android 11+ และ AGP 4.2+ รองรับ Apply Changes: อัปเดตโค้ดโดยไม่ต้องรีสตาร์ทแอปพลิเคชัน Apply Changes ทำงานผ่าน Android Runtime (ART) — มันแทนที่การทำงานของเมธอดในไฟล์ dex ทันที อย่างไรก็ตาม Apply Changes มีข้อจำกัด: ไม่ทำงานสำหรับการเปลี่ยนแปลงทรัพยากร (layout.xml, drawable), แมนิเฟสต์ และไลบรารีเนทีฟ
Apple นำเสนอ Previews (SwiftUI Preview) ใน Xcode 15 (2023) — นี่ไม่ใช่ hot reload ในความหมายดั้งเดิม Previews คอมไพล์ส่วนพรีวิวแยกจากแอปพลิเคชันหลักและแสดงผลใน canvas ของ Xcode เมื่อบันทึกไฟล์ พรีวิวจะอัปเดตใน 1–3 วินาที แต่สถานะของแอปพลิเคชันจะไม่ถูกเก็บไว้ สำหรับโปรเจกต์ UIKit, hot reload สามารถใช้งานได้ผ่านเครื่องมือของบุคคลที่สาม: InjectionIII (John Holdsworth) และ SwiftHotReload
Kotlin Multiplatform (KMP) ได้รับการสนับสนุน hot reload เชิงทดลองจาก JetBrains ตั้งแต่ปี 2024 กลไกนี้ขึ้นอยู่กับ Kotlin/Native runtime พร้อมการแทนที่ฟังก์ชันในไฟล์ออบเจ็กต์ (.klib) JetBrains Compose Multiplayer ใช้การทำงาน hot reload ของตัวเอง คล้ายกับ Flutter: การคอมไพล์แบบเพิ่มหน่วยและการแทนที่คลาสใน Kotlin/Native runtime ความเร็ว — 1–3 วินาที ใช้ได้เฉพาะการเปลี่ยนแปลง UI เท่านั้น
Apply Changes เป็นกลไกของ Android Studio ที่ใช้ API รันไทม์ ART เมื่อบันทึกโค้ด Android Studio จะกำหนดว่าคลาสใดเปลี่ยนแปลงและส่งไฟล์ dex ไปยังอุปกรณ์ผ่าน adb ART แทนที่การทำงานของเมธอดในแอปพลิเคชันที่กำลังทำงานอยู่โดยไม่หยุดมัน Apply Changes ทำงานในสามโหมด: Instant Run (การแทนที่เมธอดอย่างรวดเร็ว), Swap (การแทนที่คลาสพร้อมการสร้างอินสแตนซ์ใหม่), และ Restart Activity (หากการเปลี่ยนแปลงเข้ากันไม่ได้กับสถานะปัจจุบัน)
คำถามที่พบบ่อย
Hot Reload อัปเดตโค้ดโดยไม่ต้องรีสตาร์ทแอปพลิเคชันและรักษาสถานะ Live Reload โหลดแอปพลิเคชันหรือหน้าเว็บทั้งหมดใหม่เมื่อไฟล์เปลี่ยนแปลง Live Reload ใช้งานง่ายกว่าแต่ช้ากว่าและสูญเสียสถานะ Flutter และ React Native ใช้ hot reload; เครื่องมือเว็บใช้ live reload
Hot Reload ไม่ทำงานสำหรับการเปลี่ยนแปลงที่ต้องการการจัดสรรหน่วยความจำใหม่ (ฟิลด์คลาสใหม่), การเปลี่ยนแปลงค่าคงที่แบบสแตติก (static const), การเปลี่ยนชื่อวิดเจ็ต, การเปลี่ยนค่า enum หรือพารามิเตอร์ジェネอริก การเปลี่ยนแปลงเหล่านี้เข้ากันไม่ได้กับออบเจ็กต์ที่มีอยู่ในหน่วยความจำของ Dart VM หรือ JavaScript runtime
ใช่ hot reload ทำงานทั้งบนอุปกรณ์จริงและอีมูเลเตอร์ Flutter ส่งไฟล์เคอร์เนลไปยังอุปกรณ์ผ่าน USB (adb forward) หรือ Wi-Fi React Native ใช้ WebSocket ผ่าน Metro bundler ความล่าช้าบนอุปกรณ์จริงโดยปกติจะสูงกว่าอีมูเลเตอร์ 10–30%
Xcode Previews (ตั้งแต่ 2021) เป็นอะนาล็อกของ hot reload สำหรับ SwiftUI แต่มีข้อจำกัด: พรีวิวถูกคอมไพล์แยกต่างหาก ไม่รองรับการนำทางแอปและสถานะที่ซับซ้อน Apple ไม่ได้ให้ hot reload อย่างเป็นทางการสำหรับ iOS เครื่องมือของบุคคลที่สาม: InjectionIII และ SwiftHotReload ใช้ Objective-C Runtime สำหรับการฉีดโค้ด
หาก UI แสดงไม่ถูกต้องหลังจาก hot reload: ให้ทำ hot restart หากปัญหาอยู่ที่ข้อมูล — ตรวจสอบ callback reassemble() ใน Flutter หรือ useEffect cleanup ใน React Native สำหรับปัญหาที่เกิดขึ้นต่อเนื่อง ให้ใช้ Flutter Clean หรือ Reset Metro Cache หากบักเกิดขึ้นเฉพาะหลังจาก reload — นี่เป็นสัญญาณของความเข้ากันไม่ได้ระหว่างการเปลี่ยนแปลงกับสถานะที่มีอยู่
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม