يمكن أن تتعمق جذور أشجار أداة Flutter عميقة
جدًا ... عميقة جدًا .
تسمح الطبيعة المكونة لعناصر واجهة Flutter بتصميمات تطبيقات أنيقة للغاية ووحدات معيارية ومرنة. ومع ذلك ، يمكن أن ينتج عن هذا أيضًا الكثير من التعليمات البرمجية المعيارية لتمرير السياق. انظر ماذا يحدث عندما نريد تمرير معرف الحساب والنطاق من الصفحة إلى عنصر واجهة المستخدم على مستويين أدناه:
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) {
//
...
إذا تُرك هذا النمط بدون تحديد ، يمكن أن يتسلل بسهولة عبر قاعدة الشفرة بأكملها. لقد قمنا شخصيًا بتحديد أكثر من 30 عنصر واجهة مستخدم بهذه الطريقة. ما يقرب من نصف وقت العمل ، تلقت الأداة المعلمات فقط لتمريرها أكثر ، كما في
MyWidgetالمثال أعلاه.
حالة MyWidget مستقلة عن المعلمات ، ومع ذلك فهي تعيد البناء في كل مرة تتغير فيها المعلمات!
بالطبع ، يجب أن تكون هناك طريقة أفضل ...
تقديم InheritedWidget .
باختصار ، إنه نوع خاص من عناصر واجهة المستخدم التي تحدد السياق في جذر الشجرة الفرعية. يمكنه توفير هذا السياق بكفاءة لكل عنصر واجهة مستخدم في تلك الشجرة الفرعية. يجب أن يبدو نمط الوصول مألوفًا لمطور Flutter:
final myInheritedWidget = MyInheritedWidget.of(context);
هذا السياق هو مجرد فئة Dart. وبالتالي ، يمكن أن تحتوي على كل ما تريد وضعه هناك. العديد من سياقات Flutter شائعة الاستخدام ، مثل
Styleأو MediaQuery، ليست أكثر من InheritedWidgets التي تعيش على المستوى MaterialApp.
إذا استكملنا المثال أعلاه باستخدام InheritedWidget ، فهذا ما نحصل عليه:
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);
...
من المهم ملاحظة:
- المنشئون هم الآن
constما يجعل هذه الأدوات قابلة للتخزين المؤقت ؛ مما يزيد من الإنتاجية. - عندما يتم تحديث المعلمات ، يتم إنشاء واحدة جديدة
MyInheritedWidget. ومع ذلك ، على عكس المثال الأول ، لم يتم إعادة بناء الشجرة الفرعية. بدلاً من ذلك ، يحتفظ Flutter بسجل داخلي يتتبع عناصر واجهة المستخدم التي وصلت إلى هذاInheritedWidget، ويعيد فقط بناء تلك التي تستخدم هذا السياق. في هذا المثال ، هوMyOtherWidget. - إذا تم إعادة بناء الشجرة لأسباب أخرى غير تغييرات المعلمات ، مثل تغييرات الاتجاه ، فلا يزال بإمكان الكود الخاص بك إنشاء واحدة جديدة
InheritedWidget. ومع ذلك ، نظرًا لأن المعلمات تظل كما هي ، فلن يتم إخطار عناصر واجهة المستخدم في الشجرة الفرعية. هذا هو الغرض من الوظيفةupdateShouldNotifyالتي تنفذها لكInheritedWidget.
أخيرًا ، لنتحدث عن الممارسات الجيدة.
يجب أن تكون أداة InheritedWidget صغيرة
يؤدي التحميل الزائد لها بالكثير من السياق إلى فقدان الميزة الثانية والثالثة المذكورة أعلاه ، نظرًا لأن Flutter لا يمكنها تحديد أي جزء من السياق يتم تحديثه وأي جزء يتم استخدامه بواسطة عناصر واجهة المستخدم. في حين أن:
class MyAppContext {
int teamId;
String teamName;
int studentId;
String studentName;
int classId;
...
}
تفضل أن تفعل:
class TeamContext {
int teamId;
String teamName;
}
class StudentContext {
int studentId;
String studentName;
}
class ClassContext {
int classId;
...
}
استخدم const لإنشاء الحاجيات الخاصة بك
بدون الثبات ، لا توجد إعادة بناء انتقائية للشجرة الفرعية. ينشئ Flutter مثيلًا جديدًا لكل عنصر واجهة مستخدم في الشجرة الفرعية ويستدعيه
build()، مما يؤدي إلى إهدار الدورات الثمينة ، خاصةً إذا كانت طرق الإنشاء ثقيلة بدرجة كافية.
إبقاء العين على نطاق بك InheritedWidget-s
InheritedWidget-y يتم وضعها في جذر شجرة عنصر واجهة المستخدم. هذا ، في الواقع ، يحدد نطاقها. في فريقنا ، وجدنا أن القدرة على إعلان السياق في أي مكان في شجرة عنصر واجهة المستخدم أمر مبالغ فيه. قررنا تقييد الحاجيات السياقية لدينا لقبول Scaffold(أو مشتقاتها) فقط كأطفال. بهذه الطريقة نضمن أن السياق الأكثر دقة يمكن أن يكون على مستوى الصفحة ، ونحصل على نطاقين:
- أدوات على مستوى التطبيق مثل
MediaQuery. وهي متاحة لأي عنصر واجهة مستخدم في أي صفحة في التطبيق الخاص بك ، حيث إنها تقع في جذر شجرة عناصر واجهة التطبيق الخاصة بك. - أدوات على مستوى الصفحة مثل
MyInheritedWidgetالمثال أعلاه.
يجب عليك اختيار واحد أو آخر حسب المكان الذي ينطبق فيه السياق.
لا يمكن لعناصر واجهة المستخدم على مستوى الصفحة اجتياز حدود المسار
يبدو واضحا. ومع ذلك ، فإن هذا له آثار خطيرة لأن معظم التطبيقات لديها أكثر من مستوى واحد من التنقل. هذا ما قد يبدو عليه تطبيقك:
> School App [App Context]
> Student [Student Context]
> Grades
> Bio
> Teacher [Teacher Context]
> Courses
> Bio
هذا ما يراه Flutter:
> School App [App Context]
> Student [Student Context]
> Student Grades
> Student Bio
> Teacher [Teacher Context]
> Teacher Courses
> Teacher Bio
من منظور Flutter ، لا يوجد تسلسل هرمي للتنقل. كل صفحة (أو سقالة) عبارة عن شجرة من عناصر واجهة المستخدم المرتبطة بعنصر واجهة مستخدم التطبيق. لذلك ، عند استخدام
Navigator.pushهذه الصفحات للعرض ، فإنها لا ترث عنصر واجهة المستخدم الذي يحمل السياق الأصلي. في المثال أعلاه ، ستحتاج إلى تمرير السياق صراحةً Studentمن صفحة الطالب إلى صفحة السيرة الذاتية للطالب.
على الرغم من وجود طرق مختلفة لتمرير السياق ، أقترح تحديد مسارات بالطريقة القديمة (على سبيل المثال ، ترميز URL إذا كنت تستخدم مسارات مسماة). كما أنه يضمن إمكانية إنشاء الصفحات استنادًا إلى المسار تمامًا دون الحاجة إلى استخدام سياق الصفحة الرئيسية.
ترميز سعيد!
كن في الوقت المناسب للدورة!