const, keys और रिकंसीलिएशन की बदौलत बेकार रीबिल्ड खत्म करें, फिर DevTools से मापें।
इस पाठ को Kodokon में खोलेंFlutter तीन ट्री का संचालन करता है: विजेट (अपरिवर्तनीय, फेंकने योग्य कॉन्फ़िगरेशन), Element (जीवित इंस्टेंस जो स्टेट धारण करते हैं) और render object (लेआउट, पेंटिंग)। किसी विजेट को दोबारा बनाना सस्ता है; जो महँगा है वह है लेआउट और शृंखलाबद्ध पेंट। किसी रीबिल्ड के दौरान, Element.updateChild क्रम से तीन नियम लागू करता है: यदि नया विजेट पुराने के समरूप (एक ही इंस्टेंस) है, तो पूरा सबट्री शॉर्ट-सर्किट कर दिया जाता है; यदि Widget.canUpdate सही है (वही runtimeType, वही key), तो Element को रखा जाता है और बस पुनः कॉन्फ़िगर किया जाता है; अन्यथा, Element को नष्ट कर फिर से inflate किया जाता है, स्टेट सहित। const कीवर्ड पहले नियम का लाभ उठाता है: एक स्थिर व्यंजक को कम्पाइलर द्वारा कैननिकलाइज़ किया जाता है, हर build पर वही इंस्टेंस लौटता है, identical सही होता है - और पूरी शाखा छोड़ दी जाती है।
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() =>
_CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
const _Header(),
Text('Total: $_count'),
FilledButton(
onPressed: () => setState(() => _count++),
child: const Text('Increment'),
),
],
);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) {
debugPrint('build _Header');
return const Text('Counter');
}
}children की सूचियों के लिए, रिकंसीलिएशन पहले स्थिति के आधार पर जोड़ी बनाता है। बिना keys के stateful आइटम को पुनः क्रमबद्ध करें और हर Element अपनी स्थिति के पिछले धारक का स्टेट रख लेता है: गलत जगह टिक हुए चेकबॉक्स, आपस में बदले हुए टेक्स्ट फ़ील्ड। एक Key जोड़ी बनाने का नियम बदल देती है: एल्गोरिथ्म उसी key वाले Element को ढूँढ लेता है, भले ही उसे सूची के भीतर हिला दिया गया हो। किसी स्थिर व्यावसायिक पहचानकर्ता पर ValueKey का उपयोग करें। GlobalKey इससे भी आगे जाती है - यह आपको उसका स्टेट खोए बिना पूरे सबट्री को दोबारा पैरेंट करने देती है - पर इसकी एक वास्तविक कीमत होती है (ग्लोबल रजिस्ट्री, पूरे ट्री में विशिष्टता की बाध्यता): इसे Form या reparenting जैसी असल ज़रूरतों के लिए ही सुरक्षित रखें।
import 'package:flutter/material.dart';
class TodoList extends StatelessWidget {
const TodoList({super.key, required this.items});
final List<String> items;
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (final item in items)
Dismissible(
key: ValueKey(item),
onDismissed: (direction) {},
child: ListTile(title: Text(item)),
),
],
);
}
}अनुकूलन से पहले मापें। DevTools में, Performance व्यू दो अहम थ्रेड के लिए प्रति-फ़्रेम समय को ट्रेस करता है: UI (आपका Dart कोड: build, layout) और raster (परतों का रास्टराइज़ेशन)। 60 Hz पर बजट लगभग 16 ms है। raster की ओर का jank आपके builds को अनुकूलित करने से ठीक नहीं होता: यह पेंटिंग की लागत से आता है (छायाएँ, saveLayer, बहुत बड़े इमेज)। कौन दोबारा बना रहा है यह देखने के लिए Track widget builds सक्षम करें, और दोबारा पेंट हुए क्षेत्रों को देखने के लिए debugRepaintRainbowEnabled - हर repaint पर बॉर्डर का रंग बदलता है। RepaintBoundary किसी सबट्री को उसकी अपनी परत में पृथक कर देता है: लगातार दोबारा पेंट होता एक स्पिनर अब अपने पड़ोसियों को गंदा नहीं करता।
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugRepaintRainbowEnabled = true;
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
showPerformanceOverlay: true,
home: Scaffold(
body: Center(
child: RepaintBoundary(
child: CircularProgressIndicator(),
),
),
),
);
}
}