Mental Model Quickstart
สี่แนวคิดนี้ unlock Flutter สำหรับ React developer เมื่อเข้าใจแล้ว ส่วนที่เหลือของ framework คือการเรียน widget API เท่านั้น — mental model อยู่ในมือคุณแล้ว
1. ทุกอย่างคือ widget
หัวข้อที่มีชื่อว่า “1. ทุกอย่างคือ widget”ใน React UI ของคุณมี components (code ของคุณ) และ HTML elements (<div>, <p>, <button>) สองชั้นที่แตกต่างกัน
ใน Flutter มี ชั้นเดียวคือ widget Layout, spacing, color, text, images, gesture detection, แม้แต่ตัว app เอง — ล้วนเป็น widget ไม่มี HTML ข้างใต้ Flutter วาดทุก pixel โดยตรง
flowchart TB
subgraph React
direction TB
RApp["<App>"] --> RDiv["<div className='card'>"]
RDiv --> RH2["<h2>Hello</h2>"]
RDiv --> RBtn["<button>Click</button>"]
end
subgraph Flutter
direction TB
FApp["MyApp (StatelessWidget)"] --> FMaterial["MaterialApp"]
FMaterial --> FScaffold["Scaffold"]
FScaffold --> FCenter["Center"]
FCenter --> FColumn["Column"]
FColumn --> FText["Text('Hello')"]
FColumn --> FBtn["ElevatedButton(...)"]
end ความลึกดูมากกว่าตอนแรก แต่ consistent — ไม่มีสอง mental model ที่ต้องสลับกัน
2. Widget tree
หัวข้อที่มีชื่อว่า “2. Widget tree”Component tree ของ React และ widget tree ของ Flutter คือแนวคิดเดียวกัน ทั้งคู่เป็น description ของสิ่งที่ควร render ทั้งคู่ถูก re-evaluate เมื่อ state เปลี่ยน Framework diff description กับ snapshot ก่อนหน้าและอัปเดตเฉพาะที่เปลี่ยน
// React component treefunction Card({ title, body }) { return ( <div className="card"> <h3>{title}</h3> <p>{body}</p> </div> );}
function App() { return ( <main> <Card title="Flutter" body="Everything is a widget" /> <Card title="React" body="Everything is a component" /> </main> );}// Flutter widget treeclass InfoCard extends StatelessWidget { final String title; final String body; const InfoCard({super.key, required this.title, required this.body});
@override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text(body), ], ), ), ); }}
class MyApp extends StatelessWidget { const MyApp({super.key});
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Column( children: const [ InfoCard(title: 'Flutter', body: 'Everything is a widget'), InfoCard(title: 'React', body: 'Everything is a component'), ], ), ), ); }}3. Hot Reload ≈ Fast Refresh
หัวข้อที่มีชื่อว่า “3. Hot Reload ≈ Fast Refresh”Save file → app ที่รันอยู่ update ภายในไม่ถึงวินาทีโดยไม่ต้อง reset state นี่คือ Hot Reload ของ Flutter และทำงานเหมือน Fast Refresh ของ React ทุกประการ:
- Edit
build()methods ถูก inject ทันที - Widget state ถูก preserve (counter ยังคงจำตัวเลขอยู่)
- การเปลี่ยนแปลงใน
initState(), class fields, หรือmain()ต้องใช้ Hot Restart (R) — เหมือน changes ใน module-level code ของ React ที่ต้อง refresh เอง
4. Declarative UI — แนวคิดเดิมที่คุณรู้
หัวข้อที่มีชื่อว่า “4. Declarative UI — แนวคิดเดิมที่คุณรู้”Flutter เป็น declarative คุณ describe ว่า UI ควรหน้าตาเป็นอย่างไรสำหรับ state ที่ให้มา — ไม่ใช่ imperatively mutate DOM นี่คือ contract เดียวกับที่ React นำมาใช้
// Imperative (jQuery-style thinking — อย่าทำแบบนี้)// "find the Text widget and update its text to '$_count'"
// Declarative (Flutter way)// "given _count, build this subtree"Text('Count: $_count')เมื่อ setState รัน Flutter เรียก build() อีกครั้งและ Text widget ได้รับค่าใหม่ คุณ describe ผลลัพธ์ Flutter หาว่าต้อง update อะไรบ้าง
Runnable example — สี่แนวคิดรวมกัน
หัวข้อที่มีชื่อว่า “Runnable example — สี่แนวคิดรวมกัน”import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
// 1. ทุกอย่างคือ widget (แม้แต่ app root)
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
// 2. Widget tree: MyApp > MaterialApp > Scaffold > ...
home: const WidgetTreeDemo(),
);
}
}
// 4. Declarative: build() describe UI สำหรับ state ปัจจุบัน
class WidgetTreeDemo extends StatefulWidget {
const WidgetTreeDemo({super.key});
@override
State<WidgetTreeDemo> createState() => _WidgetTreeDemoState();
}
class _WidgetTreeDemoState extends State<WidgetTreeDemo> {
int _likes = 0;
bool _bookmarked = false;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('Mental Model Demo'),
backgroundColor: colors.primaryContainer,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 3. ลอง edit Text นี้แล้ว hot-reload ดู
Text(
'Flutter Mental Model',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
const Text(
'Everything is a widget. The tree is declarative. '
'Hot Reload keeps your state while updating the UI.',
),
const SizedBox(height: 24),
Row(
children: [
FilledButton.icon(
onPressed: () => setState(() => _likes++),
icon: const Icon(Icons.favorite),
label: Text('$_likes Likes'),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: () => setState(() => _bookmarked = !_bookmarked),
icon: Icon(
_bookmarked ? Icons.bookmark : Icons.bookmark_border,
),
label: Text(_bookmarked ? 'Saved' : 'Save'),
),
],
),
],
),
),
);
}
}