
يمكن العثور على المعلمة key
في كل مُنشئ عنصر واجهة تعامل مستخدم تقريبًا ، ولكن نادرًا ما يتم استخدام هذه المعلمة في التطوير. تحتفظ Keys
بالحالة عند تحريك عناصر واجهة التعامل في شجرة عنصر واجهة التعامل. في الممارسة العملية ، هذا يعني أنها يمكن أن تكون مفيدة لحفظ موقع التمرير للمستخدم أو حالة الحفظ عندما تتغير المجموعة.
هذه المقالة مقتبسة من الفيديو التالي. إذا كنت تفضل الاستماع / المشاهدة بدلاً من القراءة ، فسيزودك الفيديو بنفس المادة.
معظم الوقت ... لا تحتاج إلى keys
. بشكل عام ، لا يوجد أي ضرر في إضافتها ، لكن هذا ليس ضروريًا أيضًا ، حيث إنها تحدث ببساطة ككلمة رئيسية جديدة أو إعلان نوع على جانبي متغير جديد (أتحدث عنك ، Map<Foo, Bar> aMap = Map<Foo, Bar>()
).
ولكن إذا وجدت أنك تقوم بإضافة عناصر واجهة مستخدم أو إزالتها أو إعادة ترتيبها في المجموعة التي تحتوي على حالة ما ولها نفس النوع ، فيجب عليك الانتباه إلى keys
!
لشرح سبب احتياجك keys
عند تغيير مجموعة من الأدوات المصغّرة ، كتبت تطبيقًا بسيطًا للغاية يحتوي على اثنين من الأدوات المصغّرة الملونة التي تغير الأماكن عند النقر فوق زر:

في هذا الإصدار من التطبيق ، لديّ عنصران أساسيان عديمي الجنسية من اللون العشوائي ( StatelessWidget
) في عنصر واجهة StatefulWidget
Row
و PositionedTiles مع الحالة ( StatefulWidget
) لتخزين ترتيب عناصر واجهة تعامل اللون فيه. عندما أقوم بالنقر فوق الزر FloatingActionButton
في الأسفل ، فإن عناصر واجهة المستخدم الملونة تغير مكانها في القائمة بشكل صحيح:
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ StatelessColorfulTile(), StatelessColorfulTile(), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatelessColorfulTile extends StatelessWidget { Color myColor = UniqueColorGenerator.getColor(); @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding(padding: EdgeInsets.all(70.0))); } }
ولكن إذا أضفنا حالة إلى عناصر واجهة تعاملنا اللونية ( StatefulWidget
) وقمنا بتخزين الألوان فيها ، فعندما نضغط على الزر يبدو أنه لا يحدث شيء:

List<Widget> tiles = [ StatefulColorfulTile(), StatefulColorfulTile(), ]; ... class StatefulColorfulTile extends StatefulWidget { @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } }
كتفسير: الكود أعلاه هو عربات التي تجرها الدواب لأنه لا يظهر تبادل اللون عندما ينقر المستخدم على الزر. لإصلاح هذا الخطأ ، تحتاج إلى إضافة المعلمة key
إلى الحاجيات الملونة StatefulWidget
، ومن ثم سيتم تبديل الحاجيات كما نريد:

List<Widget> tiles = [ StatefulColorfulTile(key: UniqueKey()),
ولكن هذا ضروري فقط إذا كان لديك عناصر واجهة مستخدم مع حالة في الشجرة الفرعية التي تقوم بتغييرها. إذا كانت الشجرة الفرعية بالكامل لعنصر واجهة المستخدم في مجموعتك لا تحتوي على حالة ، فلن تكون هناك حاجة إلى مفاتيح.
ها أنت ذا! الكل في الكل ، كل ما تحتاج إلى معرفته لاستخدام keys
في Flutter
. ولكن إذا كنت تريد أن تذهب أكثر عمقا في ما يحدث ...
فهم لماذا keys
ضرورية في بعض الأحيان
أنت لا تزال هنا ، أليس كذلك؟ حسناً ، اقترب أكثر فأكثر لمعرفة الطبيعة الحقيقية لأشجار العناصر والحاجيات لتصبح رفرفة Mage! Uhahaha! ها ها ها! ها ها ها! ام اسف
كما تعلمون ، داخل كل عنصر واجهة مستخدم Flutter يبني العنصر المقابل. تمامًا كما يبني Flutter شجرة عنصر واجهة تعامل مستخدم ، فإنه ينشئ أيضًا شجرة عنصر (ElementTree). ElementTree بسيط للغاية ، فهو يحتوي فقط على معلومات النوع الخاصة بكل عنصر واجهة مستخدم ورابط إلى العناصر الفرعية. يمكنك التفكير في التفكير في ElementTree كهيكل عظمي لتطبيق Flutter. يعرض بنية التطبيق الخاص بك ، ولكن يمكن العثور على جميع المعلومات الإضافية على الرابط إلى عنصر واجهة المستخدم المصدر.
يحتوي عنصر واجهة تعامل الصفوف في المثال أعلاه على مجموعة من فتحات مرتبة لكل من أبنائها. عندما نقوم بإعادة ترتيب عناصر واجهة المستخدم الملونة في الصف ، يسير Flutter حول ElementTree للتحقق مما إذا كانت بنية الهيكل العظمي للتطبيق هي نفسها.

يبدأ التحقق من الصحة باستخدام RowElement ، ثم ينتقل إلى العناصر الفرعية. يتحقق ElementTree من أن عنصر واجهة المستخدم الجديد له نفس نوع key
القديم مثل key
القديم ، وإذا كان الأمر كذلك ، فإن العنصر يقوم بتحديث ارتباطه إلى عنصر واجهة المستخدم الجديد. في الإصدار عديمي الجنسية من الشفرة ، لا تحتوي عناصر واجهة الاستخدام على key
، لذلك يقوم Flutter ببساطة بفحص النوع. (إذا كان هناك الكثير من المعلومات في وقت واحد ، فراجع الرسم البياني المتحرك أعلاه.)
أدناه ElementTree للحاجيات الدولة تبدو مختلفة قليلا. هناك عناصر واجهة مستخدم وعناصر ، كما كان من قبل ، ولكن يوجد أيضًا عنصران من عناصر الحالة لعناصر واجهة المستخدم ، ويتم تخزين معلومات الألوان فيها ، وليس في عناصر واجهة التعامل نفسها.

في حالة StatefulWidget
واجهة المستخدم StatefulWidget
الملونة بدون key
، عندما أقوم بتغيير ترتيب اثنين من الأدوات ، يتحول Flutter إلى ElementTree ، ويفحص نوع RowWidget ويقوم بتحديث الرابط. ثم يتحقق عنصر عنصر واجهة المستخدم من أن عنصر واجهة المستخدم المقابل من نفس النوع ويقوم بتحديث الرابط. يحدث الشيء نفسه مع القطعة الثانية. نظرًا لأن Flutter يستخدم ElementTree وحالته المقابلة لتحديد ما يتم عرضه فعليًا على جهازك ، من وجهة نظرنا ، يبدو أن عناصر واجهة المستخدم لم يتم تبديلها!

في الإصدار الثابت من الكود في الحاجيات الملونة مع الحالة في المُنشئ ، قمت بتعريف خاصية key
. الآن ، إذا قمنا بتغيير التطبيقات المصغّرة في Row
، Row
ذلك النوع ستطابق كما كان من قبل ، لكن القيم key
لعنصر واجهة اللون والعنصر المقابل في ElementTree ستكون مختلفة. يؤدي هذا إلى قيام Flutter بإلغاء تنشيط هذه العناصر من عناصر واجهة تعامل اللون وإزالة الروابط إليها في ElementTree ، بدءًا من الأولى ، والتي لا تتطابق مع key
.

ثم يبحث Flutter عن عناصر واجهة التعامل في عنصر Row
في ElementTree باستخدام key
المقابل. إذا كان يطابق ، فإنه يضيف رابط إلى عنصر واجهة المستخدم. رفرفة يفعل لكل طفل دون رابط. الآن سوف يعرض Flutter ما نتوقعه ، ستغير عناصر واجهة تعامل اللون الأماكن عند النقر فوق الزر.
وبالتالي ، تكون keys
مفيدة إذا قمت بتغيير ترتيب عناصر واجهة المستخدم أو عددها مع الحالة في المجموعة. في هذا المثال ، قمت بحفظ اللون. ومع ذلك ، في كثير من الأحيان الشرط ليس واضحا جدا. لعب الرسوم المتحركة ، وعرض إدخال المستخدم ، والتمرير من خلال موقع - كل شيء لديه حالة.
متى يجب علي استخدام keys
؟
إجابة مختصرة: إذا كنت بحاجة إلى إضافة keys
إلى التطبيق ، فيجب عليك إضافتها في الجزء العلوي من الشجرة الفرعية للقطعة مع الحالة التي تريد حفظها.
هناك خطأ شائع رأيته هو أن الناس يعتقدون أنهم بحاجة إلى تحديد key
فقط لعنصر واجهة المستخدم الأول مع الحالة ، ولكن هناك فروق دقيقة. لا تصدقني؟ لإظهار المشكلات التي قد نشهدها ، قمت بلف عناصر واجهة التعامل بالألوان الخاصة بي في عناصر واجهة Padding
، مع ترك مفاتيح عناصر واجهة تعامل اللون.
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> {
الآن ، بلمسة زر واحدة ، تحصل التطبيقات المصغّرة على ألوان عشوائية تمامًا!

هذه هي الطريقة التي تبدو بها شجرة عنصر واجهة المستخدم و ElementTree مع عناصر واجهة مستخدم Padding المضافة:

عندما نغير مواضع عناصر واجهة المستخدم الفرعية ، فإن الخوارزمية المتطابقة بين العناصر والحاجيات تظهر مستوى واحد في شجرة العناصر. في المخطط ، يتم تظليل أطفال الأطفال بحيث لا يصرف الانتباه عن المستوى الأول. في هذا المستوى ، كل شيء يطابق بشكل صحيح.

في المستوى الثاني ، يلاحظ Flutter أن key
عنصر اللون لا يتطابق مع key
عنصر واجهة المستخدم ، لذلك يقوم بإلغاء تنشيط هذا العنصر وتجاهله وإزالة جميع الروابط إليه. keys
التي نستخدمها في هذا المثال هي LocalKeys
. هذا يعني أنه عند مطابقة عنصر واجهة مستخدم مع عناصر ، يبحث Flutter عن keys
فقط عند مستوى معين من الشجرة.
نظرًا لأنه لا يمكنه العثور على عنصر عنصر واجهة تعامل اللون في هذا المستوى مع key
المقابل ، فإنه ينشئ عنصرًا جديدًا ويقوم بتهيئة حالة جديدة ، مما يجعل عنصر واجهة المستخدم باللون البرتقالي في هذه الحالة!

إذا حددنا keys
لعناصر واجهة التعامل Padding
:
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ Padding(
يلاحظ Flutter المشكلة ويقوم بتحديث الروابط بشكل صحيح ، كما كان في مثالنا السابق. يتم استعادة النظام في الكون.

ما نوع Key
يجب استخدامه؟
أتاح لنا واجهات برمجة التطبيقات (API) الخاصة بالرفرفة إمكانية الاختيار بين عدة فئات أساسية. يعتمد نوع key
الذي يجب استخدامه على ما هي الميزة المميزة للعناصر التي تحتاج إلى keys
. انظر إلى المعلومات التي تخزنها في عناصر واجهة المستخدم المعنية.
فكر في تطبيق المهام التالية [1] ، حيث يمكنك تغيير ترتيب العناصر في قائمة المهام حسب الأولوية ، وعند الانتهاء من ذلك ، يمكنك حذفها.

ValueKey
في هذه الحالة ، يمكننا أن نتوقع أن يكون نص الفقرة المتعلقة بالتنفيذ دائمًا وفريدًا. إذا كان الأمر كذلك ، فمن المحتمل أن يكون هذا مرشحًا جيدًا لـ ValueKey
، حيث يكون النص "قيمة".
return TodoItem( key: ValueKey(todo.task), todo: todo, onDismissed: (direction) => _removeTodo(context, todo), );
ObjectKey
بدلاً من ذلك ، قد يكون لديك تطبيق دفتر العناوين ، الذي يسرد المعلومات حول كل مستخدم. في هذه الحالة ، تخزن كل عنصر واجهة مستخدم مجموعة أكثر تعقيدًا من البيانات. قد يتزامن أي حقل من الحقول الفردية ، على سبيل المثال ، الاسم أو تاريخ الميلاد ، مع إدخال آخر ، لكن المجموعة فريدة من نوعها. في هذه الحالة ، يكون ObjectKey
هو الأنسب.

UniqueKey
إذا كان لديك العديد من عناصر واجهة المستخدم في المجموعة بنفس القيمة أو إذا كنت تريد أن تتأكد حقًا من أن كل عنصر واجهة مستخدم مختلف عن الآخرين ، فيمكنك استخدام UniqueKey
. لقد استخدمت UniqueKey
في تطبيق المثال لتبديل الألوان ، لأننا لم يكن لدينا بيانات ثابتة أخرى يمكن تخزينها في عناصر واجهة المستخدم الخاصة بنا ، ولم نكن نعرف اللون الذي سيكون عليه عنصر واجهة المستخدم عند إنشائه.
ومع ذلك ، هناك شيء واحد لا تريد استخدامه لأن key
هو رقم عشوائي. في كل مرة يتم إنشاء عنصر واجهة تعامل مستخدم ، سيتم إنشاء رقم عشوائي جديد وستفقد الاتساق بين الإطارات. في هذا السيناريو ، لا يجوز لك استخدام keys
على الإطلاق!
PageStorageKeys
PageStorageKeys
هي keys
متخصصة تحتوي على الحالة الحالية للتمرير بحيث يمكن للتطبيق حفظه لاستخدامه لاحقًا.

GlobalKeys
هناك خياران لاستخدام GlobalKeys
: وهما يسمحان GlobalKeys
واجهة المستخدم بتغيير الآباء في أي مكان في التطبيق دون فقد الحالة ويمكن استخدامها للوصول إلى معلومات حول عنصر واجهة مستخدم آخر في جزء مختلف تمامًا من شجرة عنصر واجهة المستخدم. كمثال على السيناريو الأول ، يمكنك أن تتخيل أنك تريد عرض القطعة نفسها على شاشتين مختلفتين ، ولكن بنفس الحالة ، لكي يتم حفظ بيانات عنصر واجهة المستخدم ، ستستخدم GlobalKey
. في الحالة الثانية ، قد ينشأ موقف عندما تحتاج إلى التحقق من كلمة المرور ، لكنك لا ترغب في مشاركة معلومات الحالة مع عناصر واجهة مستخدم أخرى في الشجرة. يمكن أن تكون GlobalKeys
مفيدة للاختبار ، وذلك باستخدام key
للوصول إلى عنصر واجهة مستخدم معين وطلب معلومات حول حالته.

غالبًا (ولكن ليس دائمًا!) GlobalKeys
بعض المتغيرات العالمية. غالبًا ما يمكن استبدالها بـ InheritedWidgets
أو شيء مثل Redux ، أو قالب BLoC.
استنتاج موجز
بشكل عام ، استخدم Keys
إذا كنت تريد الحفاظ على الحالة بين مجموعات الأدوات الفرعية. يحدث هذا غالبًا عند تغيير مجموعة عناصر واجهة المستخدم من نفس النوع. ضع key
في الجزء العلوي من الشجرة الفرعية للقطعة التي تريد حفظها ، وحدد نوع key
استنادًا إلى البيانات المخزنة في عنصر واجهة المستخدم.
مبروك ، أنت الآن في طريقك لتصبح رفرفة ماجى! قلت يا ساحر؟ قصدت الساحر [2] ، مثل الشخص الذي يكتب شفرة مصدر التطبيق ... وهي جيدة تقريبًا. ... تقريبا.
[1] مصدر إلهام لكتابة رمز طلب تأليف تم استلامه هنا
https://github.com/brianegan/flutter_architecture_samples/tree/master/vanilla
[2] يستخدم المؤلف كلمة sorcerer
ويضيف لاحقًا خطابًا إضافيًا قبل sourcerer