Akar pohon widget Flutter bisa sangat dalam ...
Sangat dalam.
Sifat komponen widget Flutter memungkinkan desain aplikasi yang sangat elegan, modular, dan fleksibel. Namun, ini juga dapat menghasilkan banyak kode boilerplate untuk meneruskan konteks. Lihat apa yang terjadi jika kita ingin meneruskan accountId dan scopeId dari halaman ke widget dua tingkat di bawah ini:
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyWidget(accountId, scopeId);
}
}
class MyWidget extends StatelessWidget {
final int accountId;
final int scopeId;
MyWidget(this.accountId, this.scopeId);
Widget build(BuildContext context) {
// -
new MyOtherWidget(accountId, scopeId);
...
}
}
class MyOtherWidget extends StatelessWidget {
final int accountId;
final int scopeId;
MyOtherWidget(this.accountId, this.scopeId);
Widget build(BuildContext context) {
//
...
Jika tidak dicentang, pola ini dapat dengan mudah menyebar ke seluruh basis kode. Kami secara pribadi telah membuat parameter lebih dari 30 widget dengan cara ini. Hampir setengah dari waktu kerja, widget menerima parameter hanya untuk meneruskannya lebih jauh, seperti pada
MyWidgetcontoh di atas.
Status MyWidget tidak bergantung pada parameter, namun dibangun kembali setiap kali parameter berubah!
Tentu saja, harus ada cara yang lebih baik ...
Memperkenalkan InheritedWidget .
Singkatnya, ini adalah jenis widget khusus yang mendefinisikan konteks di root subpohon. Ini dapat secara efisien memberikan konteks ini ke setiap widget di subpohon itu. Pola akses seharusnya terlihat familier bagi developer Flutter:
final myInheritedWidget = MyInheritedWidget.of(context);
Konteks ini hanyalah kelas Dart. Jadi, itu bisa berisi apa pun yang Anda inginkan untuk barang di sana. Banyak konteks Flutter yang umum digunakan, seperti
Styleatau MediaQuery, tidak lebih dari InheritedWidgets yang aktif di level tersebut MaterialApp.
Jika kami melengkapi contoh di atas menggunakan InheritedWidget, inilah yang kami dapatkan:
class MyInheritedWidget extends InheritedWidget {
final int accountId;
final int scopeId;
MyInheritedWidget(accountId, scopeId, child): super(child);
@override
bool updateShouldNotify(MyInheritedWidget old) =>
accountId != old.accountId || scopeId != old.scopeId;
}
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyInheritedWidget(
accountId,
scopeId,
const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget();
Widget build(BuildContext context) {
// -
const MyOtherWidget();
...
}
}
class MyOtherWidget extends StatelessWidget {
const MyOtherWidget();
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
print(myInheritedWidget.scopeId);
print(myInheritedWidget.accountId);
...
Penting untuk diperhatikan:
- Konstruktor sekarang
constyang membuat widget ini dapat disimpan dalam cache; yang meningkatkan produktivitas. - Ketika parameter diperbarui, yang baru dibuat
MyInheritedWidget. Namun, tidak seperti contoh pertama, subpohon tidak dibangun kembali. Alih-alih, Flutter memelihara registri internal yang melacak widget yang telah mengakses iniInheritedWidget, dan hanya membangun kembali yang menggunakan konteks itu. Dalam contoh ini, ituMyOtherWidget. - Jika hierarki dibuat ulang untuk alasan selain perubahan parameter, seperti perubahan orientasi, kode Anda masih bisa membuat yang baru
InheritedWidget. Namun, karena parameternya tetap sama, widget di subpohon tidak akan diberitahukan. Ini adalah tujuan dari fungsi yangupdateShouldNotifydiimplementasikan oleh AndaInheritedWidget.
Terakhir, mari kita bahas tentang praktik yang baik.
InheritedWidget harus berukuran kecil
Membebani mereka dengan banyak konteks menyebabkan hilangnya keuntungan kedua dan ketiga yang disebutkan di atas, karena Flutter tidak dapat menentukan bagian mana dari konteks yang sedang diperbarui dan bagian mana yang sedang digunakan oleh widget. Sebagai gantinya:
class MyAppContext {
int teamId;
String teamName;
int studentId;
String studentName;
int classId;
...
}
Lebih suka melakukan:
class TeamContext {
int teamId;
String teamName;
}
class StudentContext {
int studentId;
String studentName;
}
class ClassContext {
int classId;
...
}
Gunakan const untuk membuat widget Anda
Tanpa const, tidak ada pembangunan kembali subtree secara selektif. Flutter membuat instance baru dari setiap widget di subtree dan memanggilnya
build(), membuang-buang siklus yang berharga, terutama jika metode build Anda cukup berat.
Perhatikan ruang lingkup InheritedWidget-s Anda
InheritedWidget-y ditempatkan di root pohon widget. Ini, pada kenyataannya, menentukan ruang lingkup mereka. Di tim kami, kami menemukan bahwa kemampuan mendeklarasikan konteks di mana pun di pohon widget itu berlebihan. Kami memutuskan untuk membatasi widget kontekstual kami untuk hanya menerima Scaffold(atau turunannya) sebagai anak-anak. Dengan cara ini kami memastikan bahwa konteks yang paling terperinci dapat berada di tingkat halaman, dan kami mendapatkan dua cakupan:
- Widget tingkat aplikasi seperti
MediaQuery. Mereka tersedia untuk setiap widget pada halaman manapun dalam aplikasi Anda, karena mereka berada di akar pohon widget aplikasi Anda. - Widget tingkat halaman seperti
MyInheritedWidgetcontoh di atas.
Anda harus memilih satu atau lainnya tergantung di mana konteksnya berlaku.
Widget tingkat halaman tidak dapat melintasi batas rute
Sepertinya sudah jelas. Namun, ini memiliki implikasi serius karena sebagian besar aplikasi memiliki lebih dari satu tingkat navigasi. Seperti inilah tampilan aplikasi Anda:
> School App [App Context]
> Student [Student Context]
> Grades
> Bio
> Teacher [Teacher Context]
> Courses
> Bio
Inilah yang dilihat Flutter:
> School App [App Context]
> Student [Student Context]
> Student Grades
> Student Bio
> Teacher [Teacher Context]
> Teacher Courses
> Teacher Bio
Dari perspektif Flutter, tidak ada hierarki navigasi. Setiap halaman (atau scaffold) adalah pohon widget yang terkait dengan widget aplikasi. Oleh karena itu, saat Anda menggunakan
Navigator.pushhalaman ini untuk menampilkan, mereka tidak mewarisi widget yang membawa konteks induk. Dalam contoh di atas, Anda harus secara eksplisit meneruskan konteks Studentdari halaman Siswa ke halaman Biografi Siswa.
Meskipun ada berbagai cara untuk meneruskan konteks, saya menyarankan parameterisasi rute dengan cara lama (misalnya, pengkodean URL jika Anda menggunakan rute bernama). Ini juga memastikan bahwa halaman dapat dibangun murni berdasarkan rute tanpa harus menggunakan konteks halaman induknya.
Selamat membuat kode!
Tepat waktu untuk kursus!