ข้ามไปยังเนื้อหา

build() vs render()

ใน React component function (หรือ render() ใน class components) ถูกเรียกทุกครั้งที่ state หรือ props เปลี่ยน function นั้นต้องเป็น pure function: ถ้า state และ props เหมือนกัน ก็ต้อง return JSX เหมือนกัน โดยไม่มี side effects ใน body

build() method ของ Flutter ปฏิบัติตาม contract เดียวกันทุกประการ framework เรียก build() ทุกครั้งที่ rebuild ถูก trigger โดย setState, parent rebuild, หรือการเปลี่ยนแปลง InheritedWidget และ build() ต้อง pure และราคาถูก: ห้าม network calls, ห้าม Future.wait, ห้าม computation หนัก — เป็นแค่การบรรยาย UI

กฎจริงของทั้งสอง framework นั้นเหมือนกัน: describe, don’t do (บรรยาย อย่าทำ)

React
// React — component function คือ "render"
function UserCard({ userId }) {
const [user, setUser] = useState(null);
// Side effects ไปอยู่ใน useEffect ไม่ใช่ใน render body
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// render body: pure description เท่านั้น
if (!user) return <p>Loading...</p>;
return (
<div className="card">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Flutter
class UserCard extends StatefulWidget {
final String userId;
const UserCard({super.key, required this.userId});
@override
State<UserCard> createState() => _UserCardState();
}
class _UserCardState extends State<UserCard> {
User? _user;
// Side effects ไปอยู่ใน initState / didUpdateWidget ไม่ใช่ใน build()
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final u = await fetchUser(widget.userId);
if (mounted) setState(() => _user = u);
}
// build(): pure description เท่านั้น
@override
Widget build(BuildContext context) {
if (_user == null) return const CircularProgressIndicator();
return Card(
child: Column(children: [
Text(_user!.name,
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(_user!.email),
]),
);
}
}
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal)),
      home: const Scaffold(body: Center(child: RebuildDemo())),
    );
  }
}

// Widget นี้นับว่า build() ถูกเรียกกี่ครั้ง —
// แสดงให้เห็นว่า build() ราคาถูกและทำงานบ่อย
class RebuildDemo extends StatefulWidget {
  const RebuildDemo({super.key});

  @override
  State<RebuildDemo> createState() => _RebuildDemoState();
}

class _RebuildDemoState extends State<RebuildDemo> {
  int _counter = 0;
  int _buildCount = 0; // เพิ่มใน build เพื่อแสดงความถี่

  @override
  Widget build(BuildContext context) {
    // ติดตาม rebuild count — นี่คือ "side effect" ที่ยอมรับได้เพียงอย่างเดียว
    // ใน build(): การเพิ่ม field แบบ synchronous เพื่อ demo
    _buildCount++;

    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('Counter: $_counter',
              style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text('build() called: $_buildCount times',
              style: const TextStyle(color: Colors.grey)),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: () => setState(() => _counter++),
            child: const Text('Increment (triggers rebuild)'),
          ),
        ],
      ),
    );
  }
}
เทียบเท่า Flutter ของกฎ React "ห้าม side effects ใน render body" คืออะไร?
ทำไมต้องตรวจ `if (mounted)` ก่อนเรียก setState หลัง await?
คุณเข้าถึง configuration fields ของ StatefulWidget จากใน State class ได้อย่างไร?