แบ่งปันสถานะของแอปพลิเคชันด้วย ChangeNotifier และ Provider พร้อมเชี่ยวชาญ watch, read และ select เพื่อการรีบิลด์แบบเจาะจง
เปิดบทเรียนนี้ใน KodokonsetState เพียงพอตราบใดที่สถานะเกิดและดับไปพร้อมกับหน้าจอเดียว แต่ทันทีที่ข้อมูลถูกใช้ร่วมกัน ไม่ว่าจะเป็นตะกร้าสินค้า เซสชัน หรือการตั้งค่า การส่งมันลงไปจากคอนสตรักเตอร์สู่คอนสตรักเตอร์ก็จะจัดการไม่ไหว ChangeNotifier มอบ observable ขั้นต่ำที่สุดของ SDK ส่วน provider ฉีดมันเข้าไปในทรี (tree) และรีบิลด์เฉพาะวิดเจ็ตที่สมัครรับ (subscribe) ไว้เท่านั้น เมื่อเทียบกับ Riverpod หรือ Bloc คู่นี้ยังคงตรวจสอบได้ง่ายที่สุด มีความมหัศจรรย์ซ่อนอยู่น้อย ใช้ดีเพนเดนซีเพียงตัวเดียว และเป็นกลไกที่คุณอธิบายได้ในประโยคเดียว
flutter pub add providernotifier ที่ออกแบบมาดีย่อม ห่อหุ้ม (encapsulate) ข้อมูล นั่นคือ ฟิลด์ที่เป็นส่วนตัว getter แบบอ่านอย่างเดียว และการเปลี่ยนแปลงค่าผ่านเมท็อดที่ลงท้ายด้วย notifyListeners() เท่านั้น สัญญานี้เองที่รับประกันว่าไม่มีการเปลี่ยนสถานะใดหลุดรอดสายตา UI ไปได้
import 'package:flutter/foundation.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
int get count => _items.length;
void add(String item) {
_items.add(item);
notifyListeners();
}
void remove(String item) {
_items.remove(item);
notifyListeners();
}
}สามช่องทางเข้า สามการใช้งาน context.watch<T>() สมัครรับให้วิดเจ็ต มันจะรีบิลด์ทุกครั้งที่มี notifyListeners context.read<T>() อ่านโดยไม่สมัครรับ เป็นเครื่องมือสำหรับคอลแบ็กอย่าง onPressed context.select<T, R>() สมัครรับเฉพาะการฉายภาพ (projection) วิดเจ็ตจะรีบิลด์ก็ต่อเมื่อค่าที่เจาะจงนั้นเปลี่ยนแปลงเท่านั้น กฎทองคือ watch และ select ใช้ใน build เท่านั้น ส่วน read ใช้ในตัวจัดการ (handler) อย่าทำสลับกันเด็ดขาด
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel extends ChangeNotifier {
int _value = 0;
int get value => _value;
void increment() {
_value++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const CounterApp(),
),
);
}
class CounterApp extends StatelessWidget {
const CounterApp({super.key});
@override
Widget build(BuildContext context) {
final value = context.watch<CounterModel>().value;
return MaterialApp(
home: Scaffold(
body: Center(child: Text('Count: $value')),
floatingActionButton: FloatingActionButton(
onPressed: () =>
context.read<CounterModel>().increment(),
child: const Icon(Icons.add),
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
int get count => _items.length;
}
class CartBadge extends StatelessWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context) {
final count = context.select(
(CartModel cart) => cart.count,
);
return Badge(
label: Text('$count'),
child: const Icon(Icons.shopping_cart),
);
}
}
onPressed คุณจะเรียก cart.add(...) โดยไม่สร้างการสมัครรับที่ไม่จำเป็นได้อย่างไร?context.watch<CartModel>().add(...)context.read<CartModel>().add(...)context.select((CartModel c) => c.add(...))notifyListeners() กระตุ้นอะไร?List.unmodifiable(_items) แทนที่จะเป็น _items โดยตรง?notifyListeners และจึงไม่มีการรีบิลด์