aidoc.uz

2-qism · Flutter arxitekturasi·04-bob

Flutter arxitekturasi — Widget, Element va RenderObject daraxtlari

Flutter'ning ichki mexanizmi: nima uchun uchta alohida daraxt bor, BuildContext aslida nima, va setState chaqirilganda aniq nima sodir bo'ladi.

Ko'pchilik Flutter dasturchisi "widget" tushunchasi bilan ishlaydi, lekin senior darajadagi intervyularda ko'proq savol beriladigan narsa — widget aslida nima emasligi. Widget — ekranga chizilgan narsaning o'zi emas, balki uni qanday chizish kerakligi haqidagi konfiguratsiya, xolos. Shu farqni tushunmaslik — Flutter intervyularida eng ko'p uchraydigan bilim bo'shlig'i. Bu bobda Flutter'ning uchta daraxti, BuildContext aslida nima ekani va setState chaqirilganda framework ichida aniq nima ro'y berishini ko'ramiz.

1. Nega bitta emas, uchta daraxt bor#

Flutter UI'ni uchta parallel daraxt orqali boshqaradi, va har biri boshqa vazifani bajaradi:

Widget Tree
O'zgarmas (immutable) konfiguratsiya — 'nima chizilishi kerak' haqidagi tavsif, har `build()` chaqirig'ida qaytadan yaratiladi
Element Tree
Widget bilan RenderObject o'rtasidagi ko'prik — hayot davomiyligini (lifecycle) va holatni (State) saqlaydi, kamdan-kam qayta yaratiladi
RenderObject Tree
Haqiqiy layout, paint va hit-testing mantig'i shu yerda — ekranga piksel chiqaradigan qatlam

Widget'lar — engil, o'zgarmas obyektlar. Har safar build() metodi chaqirilganda, butun widget daraxti qaytadan yaratiladi — bu arzon, chunki widget'lar shunchaki ma'lumot tashuvchi klasslar (ko'pincha bir nechta final maydon bilan). Lekin ekranga chiqarish (layout hisoblash, canvas'ga chizish) qimmat operatsiya. Agar har build()da butun ekran qayta chizilsa, ilova sekinlashadi.

Shu sababli Flutter ikkita qo'shimcha daraxt yaratadi:

class MyButton extends StatelessWidget {
  final String label;
  const MyButton({super.key, required this.label});
 
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text(label), // Har build()da yangi Text widget yaratiladi
    );
  }
}

MyButton qayta build bo'lganda, yangi Text(label) obyekti yaratiladi — lekin agar ekrandagi RenderParagraph (matnni chizadigan haqiqiy render obyekti) allaqachon mavjud bo'lsa va faqat matn o'zgargan bo'lsa, Flutter eski RenderObject'ni saqlab qolib, unga yangi ma'lumotni beradi, uni qaytadan yaratmaydi. Aynan shu optimallashtirish — Element daraxtining asosiy vazifasi.

2. Element — widget bilan render orasidagi "xotira"#

Element — Flutter arxitekturasining eng kam tushuniladigan, lekin eng muhim qismi. Har bir widget createElement() metodiga ega, va u widget uchun mos Element obyektini yaratadi:

abstract class Widget {
  Element createElement();
}
 
class StatelessWidget extends Widget {
  @override
  StatelessElement createElement() => StatelessElement(this);
}
 
class StatefulWidget extends Widget {
  @override
  StatefulElement createElement() => StatefulElement(this);
}

Element uchta narsani biladi:

  1. Qaysi widget uni yaratgani (widget maydoni) — bu widget daraxti qayta build bo'lganda yangilanadi.
  2. O'zining RenderObject'i (agar bo'lsa) yoki farzand Element'lari.
  3. BuildContext sifatida o'zi ishlaydi — BuildContext interfeysi va Element klassi Flutter'da bir xil obyekt, faqat ikki xil interfeys orqali ko'rinadi.

3. Reconciliation — Flutter qachon yangi Element yaratadi, qachon eskisini saqlaydi#

Bu — arxitektura savollarining markazi. Widget qayta build bo'lganda, Flutter eski widget bilan yangi widgetni solishtiradi (Widget.canUpdate):

static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

Ikki shart: bir xil turdagi klass va bir xil key (yoki ikkalasida ham key yo'q). Agar bu shart bajarilsa, Element saqlab qolinadi — uning State obyekti (agar StatefulWidget bo'lsa) va RenderObject'i o'zgarmaydi, faqat update() chaqirilib, ichidagi widget ko'rsatkichi yangilanadi. Agar shart bajarilmasa, eski Element butunlay yo'q qilinadi (unmount(), State.dispose() chaqiriladi) va o'rniga yangi Element yaratiladi (State.initState() qaytadan ishlaydi).

// Ikkala holatda ham Column bir xil bo'lib qoladi (Column === Column, key yo'q)
// lekin ichidagi widget turi o'zgarsa, mos Element almashtiriladi:
Column(
  children: [
    showIcon ? const Icon(Icons.star) : const Text('Yulduz'),
    //  ^ turi o'zgarganda: eski Element yo'q qilinadi, yangisi yaratiladi
  ],
)

Bu — nega ro'yxat elementlari almashtirilganda (masalan, filtrlashdan keyin) State "aralashib ketishi" mumkinligining sababi: Flutter faqat turi va pozitsiyasi bo'yicha solishtiradi, key bo'lmasa mazmuni bo'yicha emas. Buni to'liq Keys mavzusida keyingi bobda ko'ramiz.

4. setState chaqirilganda aniq nima sodir bo'ladi#

setState(() { ... }) — sehrli funksiya emas, oddiy mexanizm:

  1. Callback ishga tushadi

    setState'ga berilgan funksiya darhol, sinxron ravishda chaqiriladi — u ichida State maydonlarini o'zgartirasiz (masalan, count++).

  2. Element 'dirty' deb belgilanadi

    State._element!.markNeedsBuild() chaqiriladi. Bu Element'ni framework'ning "qayta build kerak" ro'yxatiga qo'shadi. Hech qanday build hozircha sodir bo'lmaydi — bu faqat belgi.

  3. Keyingi frame'gacha kutiladi

    Flutter darhol qayta chizmaydi. markNeedsBuild() WidgetsBinding'ga "menga keyingi frame kerak" deb signal beradi, va framework barcha "dirty" Element'larni bitta frame ichida, scheduleFrame orqali qayta quradi.

  4. Faqat shu Element va uning bolalar daraxti qayta build bo'ladi

    Butun ilova emas — faqat setState chaqirilgan State'ga tegishli Element va undan pastdagilar. Ota (parent) widget'lar qayta build bo'lmaydi (agar ular ham alohida o'zgarmagan bo'lsa).

  5. Yangi widget daraxti eski Element daraxti bilan solishtiriladi

    3-bo'limdagi canUpdate mantig'i ishga tushadi: mos RenderObject'lar saqlanadi, faqat o'zgargan xususiyatlar yangilanadi.

class Counter extends StatefulWidget {
  const Counter({super.key});
  @override
  State<Counter> createState() => _CounterState();
}
 
class _CounterState extends State<Counter> {
  int _count = 0;
 
  void _increment() {
    setState(() {
      _count++; // Faqat holatni o'zgartiradi — build() shu yerda chaqirilmaydi
    });
  }
 
  @override
  Widget build(BuildContext context) {
    return Text('$_count'); // setState keyin, keyingi frame'da chaqiriladi
  }
}

5. Rendering pipeline — build'dan pikselgacha#

Bitta frame ichida Flutter to'rtta bosqichdan o'tadi:

1
Build
'Dirty' Element'lar uchun build() chaqiriladi, yangi widget subtree'lar yaratiladi va Element/RenderObject daraxtlariga moslashtiriladi
2
Layout
Har bir RenderObject ota-onasidan constraints (cheklovlar) oladi, o'z o'lchamini hisoblaydi va bolalarini joylashtiradi — bitta pastga-yuqoriga o'tish (keyingi bobda batafsil)
3
Paint
Har bir RenderObject o'zini Layer'lar (Canvas buyruqlari) ro'yxatiga chizadi — bu haligacha piksel emas, chizish buyruqlari
4
Composite
GPU Layer'larni birlashtirib, haqiqiy piksellarni ekranga chiqaradi (Skia/Impeller renderer orqali)

Muhim nuans: Layout va Paint har doim ham to'liq daraxt bo'ylab takrorlanmaydi. Agar faqat bitta RenderObject'ning rangi o'zgargan bo'lsa (o'lchami emas), Flutter faqat shu obyektni qayta chizadi (markNeedsPaint), layout'ni butunlay qayta hisoblamaydi (markNeedsLayout). Bu ikkalasi — alohida, mustaqil optimallashtirish yo'llari.

6. Element lifecycle — mount, update, unmount, deactivate#

// Konseptual, soddalashtirilgan Element lifecycle:
element.mount(parent, slot);   // Birinchi marta daraxtga qo'shiladi
element.update(newWidget);     // Mos widget bilan qayta ishlatiladi
element.deactivate();          // Daraxtdan vaqtincha olib tashlanadi (masalan, GlobalKey orqali ko'chirilmoqda)
element.activate();            // Boshqa joyga qayta qo'shilsa
element.unmount();             // Butunlay yo'q qilinadi — State.dispose() shu yerda chaqiriladi

deactivate/activate jufti — kam ma'lum, lekin muhim holat: agar widget GlobalKey bilan bir frame ichida daraxtning boshqa joyiga ko'chirilsa, Flutter uni yo'q qilib qayta yaratmaydi, balki shu Elementni yangi joyga ko'chiradi (deactivate → activate), State saqlanib qoladi. Aynan shu — GlobalKeyning eng kuchli, lekin qimmat xususiyati.

Tez-tez so'raladigan intervyu savollari#

Savol: Widget bilan Element o'rtasidagi farq nima?

Javob: Widget — o'zgarmas (immutable) konfiguratsiya obyekti, har build()da qaytadan yaratiladi va arzon. Element — shu konfiguratsiyaning daraxtdagi "joniga" mos keluvchi, hayot davomiyligi (lifecycle) bor obyekt: u Stateni, RenderObject bilan bog'lanishni va daraxtdagi joyini eslab qoladi. Widget'lar tez-tez yaratiladi va tashlanadi, lekin ularga mos Element ko'pincha reconciliation orqali saqlanib qoladi.

Savol: BuildContext va Element bir xil narsami?

Javob: Ha — Element klassi BuildContext interfeysini implement qiladi. build(BuildContext context) parametri sifatida kelayotgan narsa — aynan shu widget uchun yaratilgan Element, faqat "faqat ma'lumot o'qish" uchun cheklangan interfeys ko'rinishida.

Savol: Nega Flutter uchta alohida daraxt ishlatadi, ikkita emas?

Javob: Widget daraxti — arzon, tez-tez qayta yaratiladigan konfiguratsiya. RenderObject daraxti — qimmat, layout/paint mantig'ini bajaradigan qatlam. Agar bu ikkitasi to'g'ridan-to'g'ri bog'langan bo'lsa, har build'da RenderObject'larni ham qayta yaratishga to'g'ri kelardi. Element daraxti — ular orasidagi vositachi: widget o'zgarganda RenderObject'ni qayta ishlatish yoki yangilashga qaror qiluvchi qatlam.

Savol: setState chaqirilgach, build() darhol ishga tushadimi?

Javob: Yo'q. setState faqat Element'ni "dirty" deb belgilaydi va keyingi frame'da qayta build so'raladi. Haqiqiy build() chaqiruvi Flutter engine keyingi frame'ni chizishga tayyor bo'lganda sodir bo'ladi — odatda millisekundlar ichida, lekin sinxron emas.

Savol: Widget.canUpdate qanday ishlaydi va nega muhim?

Javob: U ikkita shartni tekshiradi: runtimeType bir xilmi va key bir xilmi. Ikkalasi ham to'g'ri bo'lsa, Element saqlanadi va faqat yangilanadi; aks holda eski Element yo'q qilinib (dispose chaqirilib), yangisi yaratiladi. Bu — reconciliationning yuragi va nega noto'g'ri/yo'q key ishlatish Statening yo'qolishiga olib kelishining sababi.

Savol: Agar parent widget qayta build bo'lsa, uning barcha farzandlari ham majburan qayta build bo'ladimi?

Javob: Yo'q, shart emas. Agar farzand const konstruktor bilan yaratilgan bo'lsa va parametrlar o'zgarmagan bo'lsa, Flutter bir xil widget instansini qayta ishlatadi va uni qayta build qilmaydi. const bo'lmagan farzandlar odatda qayta yaratiladi (yangi widget obyekti), lekin bu ularning RenderObject'i albatta qayta chizilishini anglatmaydi — reconciliation baribir ishlaydi.

Savol: Layout va Paint bosqichlari doim birga qayta ishlaydimi?

Javob: Yo'q. Bular mustaqil: markNeedsLayout() faqat o'lcham/joylashuv o'zgarganda, markNeedsPaint() faqat vizual ko'rinish (masalan, rang) o'zgarganda chaqiriladi. Faqat rangi o'zgargan widget uchun butun daraxtni qayta layout qilish shart emas — bu ikkita alohida optimallashtirish yo'li.

Savol: deactivate() va unmount() orasidagi farq nima?

Javob: deactivate() — Element daraxtdan vaqtincha olib tashlanganda chaqiriladi (masalan, GlobalKey orqali boshqa joyga ko'chirilayotganda); agar shu frame ichida qayta activate() qilinmasa, keyin unmount() chaqiriladi va State.dispose() ishga tushadi. Ya'ni deactivate — vaqtinchalik, unmount — yakuniy va qaytarib bo'lmaydigan holat.

Savol: Nega RenderObject widget'dan ancha qimmatroq hisoblanadi?

Javob: Widget — bir nechta final maydonli oddiy Dart obyekti, yaratish narxi deyarli nol. RenderObject esa layout hisoblash (constraint solve), paint buyruqlarini generatsiya qilish va GPU bilan bog'liq resurslarni boshqarish kabi og'ir ishlarni bajaradi — shuning uchun Flutter uni iloji boricha qayta ishlatishga harakat qiladi, qaytadan yaratishga emas.

Savol: InheritedWidget orqali ma'lumot olish qanday reconciliation bilan bog'liq?

Javob: context.dependOnInheritedWidgetOfExactType<T>() chaqirilganda, Element o'zini shu InheritedElementning "bog'liqlar" (dependents) ro'yxatiga yozdiradi. InheritedWidget yangilanganda (updateShouldNotify true qaytarsa), faqat shu ro'yxatdagi Element'lar markNeedsBuild() qilinadi — butun daraxt emas. Bu alohida, InheritedWidget'ga xos rebuild mexanizmi, keyingi bobda batafsil ko'riladi.

Savol: Hot reload Element daraxtiga qanday ta'sir qiladi?

Javob: Hot reload kodni almashtiradi, lekin Element va State daraxtini saqlashga harakat qiladi — shuning uchun initStateda bajarilgan ishlar (masalan, animatsiya boshlash) hot reload paytida qayta ishga tushmaydi. Faqat build() metodlari qayta chaqiriladi. Agar klass strukturasi tubdan o'zgarsa (masalan, State klassi generic parametri o'zgarsa), Flutter buni avtomatik aniqlay olmay, to'liq restart talab qilishi mumkin.

Ko'p uchraydigan xato javoblar#

Savol / vaziyatJunior darajadagi noto'g'ri javobSenior darajadagi to'g'ri javob
"Widget nima?""Ekrandagi UI elementi""UI elementining o'zgarmas konfiguratsiyasi; haqiqiy ekran holatini RenderObject ushlab turadi"
"setState nima qiladi?""Ekranni qayta chizadi""Element'ni 'dirty' deb belgilaydi va keyingi frame'da qayta build so'raydi — darhol chizmaydi"
"BuildContext nima?""build() metodining parametri, unchalik muhim emas""Element'ning o'zi — daraxt bo'ylab yuqoriga qarab ma'lumot qidirish uchun interfeys"
"Har build()da nima sodir bo'ladi?""Butun ekran qayta chiziladi""Faqat yangi widget obyektlari yaratiladi; RenderObject'lar reconciliation orqali saqlanadi yoki yangilanadi, qayta chizish shart emas"

Amaliyot#

Amaliy topshiriq

Rebuild'ni kuzatib chiqing

Bitta StatefulWidget yarating, ichida ikkita farzand widget bo'lsin: biri const bilan belgilangan, ikkinchisi — yo'q. Har ikkalasining build() metodiga debugPrint qo'shing. setState chaqirib, qaysi biri qayta build bo'lishini, qaysi biri bo'lmasligini kuzating va nega shunday bo'lishini tushuntiring.

Maslahatni ko'rsatish

StatelessWidget'ning build() metodi ichiga print() yoki debugPrint() qo'shing va qachon chaqirilishini konsolda kuzating — parent qayta build bo'lganda farzand ham chaqiriladimi?