اكتشفت مؤخرًا Flutter - إطارًا جديدًا من Google لتطوير تطبيقات الجوال عبر الأنظمة الأساسية - وقد أتيحت لي الفرصة لإظهار أساسيات Flutter لشخص لم يبرمج من قبل. تمت كتابة Flutter نفسها في Dart ، وهي لغة ولدت في متصفح Chrome والتي هربت إلى عالم وحدة التحكم - وجعلتني أفكر "هم ، لكن يمكن كتابة Flutter جيدًا في Go!"
لم لا؟ تم إنشاء كل من Go و Dart من قِبل Google ، كلتا اللغتين المترجمتين - استدار بعض الأحداث بشكل مختلف قليلاً ، سيكون Go مرشحًا ممتازًا لتنفيذ مثل هذا المشروع الكبير مثل Flutter. سيقول شخص ما - لا توجد فصول دراسية أو مواد عامة أو استثناءات في Go ، لذلك لا يناسبك.
لذلك دعونا ندعي أن Flutter مكتوب بالفعل في Go. كيف ستبدو الكود وبشكل عام ، هل ستنجح؟

ما هو الخطأ في دارت؟
أتابع هذه اللغة منذ بدايتها كبديل لجافا سكريبت في المتصفحات. تم تضمين Dart في متصفح Chrome لبعض الوقت والأمل في أن يحل محل JS. كان من المحزن أن نقرأ في مارس 2015 أنه تمت إزالة دعم Dart من Chrome .
وثبة نفسها أمر عظيم! حسنًا ، في الأساس ، بعد JavaScript ، تكون أي لغة رائعة ، ولكن بعد ، على سبيل المثال ، Go ، Dart ليست جميلة جدًا. لكن حسنا جدا. إنه يحتوي على جميع الميزات التي يمكن تصورها وغير المتصورة - الفصول ، والعامة ، والاستثناءات ، والعقود المستقبلية ، غير المتزامنة ، حلقة الحدث ، JIT / AOT ، جامع القمامة ، التحميل الزائد للوظائف - تسمية أي ميزة معروفة من نظرية لغات البرمجة وفي Dart الاحتمالات. يحتوي Dart على بناء جملة خاص لأي شريحة تقريبًا - بناء جملة خاص للأحرف / المحددات ، بناء جملة خاص للمنشئين المختصرين ، بناء جملة خاص بناء جملة خاص ، وأكثر من ذلك بكثير.
هذا يجعل دارت مباشرة من النظرة الأولى مألوفة لدى الأشخاص الذين قاموا ببرمجة بالفعل في أي لغة برمجة من قبل ، وهذا شيء رائع. لكن في محاولة لشرح كل هذه الوفرة من الميزات الخاصة في مثال "Hello، world" البسيط ، وجدت أن هذا ، على العكس من ذلك ، يجعل من الصعب إتقانه.
- كانت كل الميزات "الخاصة" للغة مربكة - "طريقة خاصة تسمى المنشئ" ، "بناء جملة خاص للتهيئة التلقائية" ، "بناء جملة خاص للمعلمات المسماة" ، إلخ.
- كان كل شيء "مخفيًا" مربكًا - "من أي ميزة استيراد هذه الوظيفة؟ إنها مخفية ، بالنظر إلى الكود الذي لا يمكنك اكتشافه" ، "لماذا يوجد مُنشئ في هذه الفئة ، ولكن ليس في هذه الفئة؟ إنه موجود ، لكنه مخفي" وما إلى ذلك
- كل شيء "غامض" مرتبك - "حتى هنا لإنشاء معلمات الدالة بأسماء أو بدونها؟" ، "هل يجب أن تكون const أو نهائية؟" ، "هنا استخدم بناء جملة الوظيفة العادية أو" اختصارها بالسهم "" "، إلخ.
من حيث المبدأ ، فإن هذا الثالوث - "الخاص" و "الخفي" و "الغامض" - ليس سيئًا يجسد جوهر ما يسميه الناس "السحر" في لغات البرمجة. هذه هي الميزات التي تم إنشاؤها لتبسيط كتابة التعليمات البرمجية ، ولكن في الواقع تعقيد قراءتها وفهمها.
وهذا هو بالضبط المجال الذي تأخذ فيه Go موقعًا مختلفًا اختلافًا جوهريًا عن غيرها من اللغات ، وتتولى الدفاع بشدة. Go هي لغة ليس بها أي سحر تقريبًا - يتم تقليل مقدار الكلمات "المخفية" و "الخاصة" و "الغامضة" فيها. لكن العودة لها عيوبها.
ما هو الخطأ في الذهاب؟
نظرًا لأننا نتحدث عن Flutter ، وهذا إطار عمل لواجهة المستخدم ، فلننظر إلى Go كأداة لوصف UI والعمل معه. بشكل عام ، تشكل أطر واجهة المستخدم تحديًا هائلاً وتتطلب دائمًا حلولًا متخصصة. أحد الأساليب الأكثر شيوعًا في واجهة المستخدم هو إنشاء DSL - لغات خاصة بالمجال - يتم تنفيذها في شكل مكتبات أو أطر عمل مصممة خصيصًا لتلبية احتياجات واجهة المستخدم. وفي أغلب الأحيان يمكنك سماع الرأي القائل بأن Go هي لغة سيئة لموضوع DSL.
في جوهرها ، يعني DSL إنشاء لغة جديدة - المصطلحات والأفعال - التي يمكن للمطور العمل عليها. يجب أن يصف الكود الموجود عليه بوضوح الميزات الرئيسية للواجهة الرسومية ومكوناتها ، وأن يكون مرنًا بما يكفي لإعطاء حرية التصور لخيال المصمم ، وفي الوقت نفسه يكون جامدًا بما يكفي لتقييده وفقًا لقواعد معينة. على سبيل المثال ، يجب أن تكون قادرًا على وضع الأزرار على بعض الحاوية ، ووضع الرمز في المكان الصحيح في هذا الزر ، ولكن يجب على المحول البرمجي إرجاع خطأ إذا حاولت إدراج الزر في نص ، على سبيل المثال.
بالإضافة إلى أن لغات وصف واجهة المستخدم غالبًا ما تكون تعريفية - مما يتيح الفرصة لوصف الواجهة في شكل "ما أريد أن أراه" ، والسماح للإطار نفسه أن يفهم من أي كود وكيفية تشغيله.
تم تطوير بعض اللغات في الأصل مع وجود مثل هذه المهام في الأفق ، ولكن ليس Go. يبدو أن كتابة الرفرفة على الذهاب ستكون مهمة أخرى!
أودا رفرفة
إذا لم تكن معتادًا على Flutter ، فإنني أوصي بشدة بقضاء عطلة نهاية الأسبوع التالية في مشاهدة مقاطع الفيديو التعليمية أو قراءة البرامج التعليمية ، والتي يوجد منها الكثير. لأن Flutter ، بلا شك ، يعكس قواعد اللعبة في تطوير تطبيقات الهاتف المحمول. وعلى الأرجح ، ليس فقط المحمول - هناك بالفعل عارضون (من حيث Flutter ، embedders) لإطلاق تطبيقات Flutter كتطبيقات dekstop أصلية ، وتطبيقات ويب .
إنه سهل التعلم ، إنه منطقي ، ويأتي مع مكتبة ضخمة من الحاجيات الجميلة على تصميم المواد (وليس فقط) ، لديه مجتمع كبير وكبير وموالفة ممتازة (إذا كنت ترغب في سهولة العمل مع go build/run/test
في Go ، ثم في Flutter سوف تحصل على تجربة مماثلة).
قبل عام ، كنت بحاجة إلى كتابة تطبيق صغير للهاتف المحمول (لنظامي التشغيل iOS و Android ، بالطبع) ، وأدركت أن تعقيد تطوير تطبيق عالي الجودة لكلا المنصتين كبير جدًا (لم يكن التطبيق هو المهمة الرئيسية) - اضطررت إلى الاستعانة بمصادر خارجية ودفع المال من أجله. في الواقع ، كانت كتابة تطبيق غير معقد ، لكن عالي الجودة والعمل على جميع الأجهزة ، مهمة مستحيلة حتى بالنسبة لشخص يتمتع بخبرة برمجة تقارب 20 عامًا. وكان دائما هراء بالنسبة لي.
مع Flutter ، أعدت كتابة هذا التطبيق في الساعة 3 بعد الظهر ، أثناء تعلم الإطار نفسه من البداية. إذا أخبرني أحدهم أن ذلك قد يكون مبكرًا ، فلن أصدق ذلك.
كانت آخر مرة رأيت فيها زيادة مماثلة في الإنتاجية مع اكتشاف التكنولوجيا الجديدة قبل 5 سنوات عندما اكتشفت Go. تلك اللحظة غيرت حياتي.
لذلك أوصي ببدء تعلم الرفرفة وهذا البرنامج التعليمي جيد جدًا .
"مرحبا ، العالم" على الرفرفة
عندما تنشئ تطبيقًا جديدًا من خلال flutter create
، ستحصل على مثل هذا البرنامج فقط مع العنوان والنص والعداد وزر يزيد العداد.

أعتقد أن هذا مثال رائع. لكتابتها على رفرفة لدينا وهمية على الذهاب. لديه تقريبا جميع المفاهيم الأساسية للإطار الذي يمكنك اختبار الفكرة عليه. لنلقِ نظرة على الكود (هذا ملف واحد):
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
دعنا نحلل الشفرة في أجزاء ، ونحلل كيف وكيف يناسبها ، ونلقي نظرة على الخيارات المختلفة التي لدينا.
نحن نترجم الكود على Go
ستكون البداية بسيطة ومباشرة - قم باستيراد التبعية وإطلاق الوظيفة main()
. لا شيء معقد أو مثير للاهتمام هنا ، التغيير شبه بناء الجملة:
package hello import "github.com/flutter/flutter" func main() { app := NewApp() flutter.Run(app) }
الفرق الوحيد هو أنه بدلاً من تشغيل MyApp()
- وهي وظيفة منشئ ، وهي وظيفة خاصة مخفية داخل فئة تسمى MyApp - نحن ببساطة ندعو الوظيفة المعتادة الصريحة وغير المخفية NewApp()
. إنها تفعل الشيء نفسه ، لكن من الواضح أن تشرح وتفهم ما هو عليه ، وكيف يبدأ وكيف يعمل.
فصول القطعة
في رفرفة ، كل شيء يتكون من الحاجيات. في إصدار Dart من Flutter ، يتم تطبيق كل عنصر واجهة مستخدم كصف ترث فئات خاصة لعناصر واجهة المستخدم من Flutter.
لا توجد فصول في Go ، وبالتالي لا يوجد تسلسل هرمي ، لأن العالم ليس موجهًا للكائنات ، بل حتى أقل هرمية. بالنسبة للمبرمجين المطلعين فقط على نموذج OOP الموجه إلى الفصل ، قد يكون هذا بمثابة كشف ، لكنه في الحقيقة ليس كذلك. العالم عبارة عن رسم بياني عملاق متشابك من المفاهيم والعمليات والتفاعلات. إنها ليست منظمة تمامًا ، ولكنها ليست أيضًا فوضوية ، ومحاولة الضغط عليها في التسلسل الهرمي للفصل هي الطريقة الأكثر موثوقية لجعل قاعدة الشفرة غير قابلة للقراءة وغير خرقاء - بالضبط ما هي معظم قواعد الشفرات في الوقت الحالي.

إنني أقدر حقًا Go لأن منشئيها عانوا من إعادة التفكير في هذا المفهوم في كل مكان وفصلوا في Go مفهوم OOP أبسط وأقوى بكثير ، والذي ، عن طريق الصدفة ، اتضح أنه أقرب إلى ما كان يفكر به مُصمم OOP ، آلان كاي.
في Go ، نمثل أي تجريد في شكل نوع محدد - بنية:
type MyApp struct {
في إصدار Dart من Flutter ، يجب على MyApp
أن يرث StatelessWidget
وتجاوز طريقة الإنشاء. هذا ضروري لحل مشكلتين:
- إعطاء القطعة لدينا (
MyApp
) بعض الخصائص / الأساليب الخاصة - مكّن Flutter من الاتصال بالرمز الخاص بنا في عملية الإنشاء / العرض
لا أعرف الأجزاء الداخلية من Flutter ، لذلك دعنا نقول أن البند رقم 1 ليس في السؤال ، وعلينا فقط أن نفعل ذلك. يحتوي Go على مثل هذا الحل الفريد والواضح لهذا: تضمين الأنواع:
type MyApp struct { flutter.Core
سيضيف هذا الرمز كل خصائص flutter.Core
والأساليب إلى نوع MyApp
بنا. لقد قمت Core
بدلاً من Widget
، أولاً ، لأن كتابة التضمين لا تجعل MyApp
لدينا عنصر واجهة مستخدم حتى الآن ، وثانياً ، يتم استخدام هذا الاسم جيدًا في إطار GopherJS Vecty (شيء مثل React ، فقط لـ Go). سوف أتطرق إلى موضوع التشابه بين فكتاي ورفرفة لاحقًا.
النقطة الثانية - تطبيق أسلوب build()
، والتي ستكون قادرة على استخدام محرك Flutter - يتم حلها أيضًا ببساطة وبشكل لا لبس فيه في Go. نحتاج فقط إلى إضافة طريقة ذات توقيع محدد تفي بواجهة معينة محددة في مكان ما في مكتبة Flutter الخيالية على Go:
flutter.go:
type Widget interface { Build(ctx BuildContext) Widget }
والآن لدينا main.go:
type MyApp struct { flutter.Core
يمكننا ملاحظة بعض الاختلافات هنا:
- الكود أكثر مطول إلى حد ما - تشير
BuildContext
و Widget
و MaterialApp
إلى استيراد flutter
أمامها. - الرمز أقل قليلاً من
@override
- لا توجد كلمات مثل extends Widget
أو @override
- تبدأ طريقة
Build()
بحرف كبير ، لأنها تعني "الدعاية" للأسلوب في Go. في Dart ، يتم تحديد الدعاية ما إذا كان الاسم يبدأ بتسطير أسفل السطر (_) أم لا.
لذلك ، لإنشاء عنصر واجهة مستخدم في Flutter on Go ، نحتاج إلى تضمين نوع flutter.Core
وتطبيق واجهة flutter.Widget
. لقد حظينا بها ، وحفرنا أكثر.
الشرط
كان هذا أحد الأشياء التي أربكتني حقًا في Flutter. هناك فئتان مختلفتان - StatelessWidget
و StatefulWidget
. بالنسبة لي ، فإن "عنصر الحالة عديم الحالة" هو نفس عنصر واجهة المستخدم ، بدون البيانات ، الحالة ، الحالة - لماذا نتحدث عن فئة جديدة؟ لكن حسنا ، أنا يمكن أن يعيش معها.
لكن - علاوة على ذلك - لا يمكنك فقط أن ترث فئة أخرى ( StatefulWidget
) ، ولكن يجب عليك كتابة هذا السحر (IDE سيفعل ذلك نيابةً عنك ، ولكن ليس النقطة):
class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold() } }
حسنًا ، دعنا نرى ما يحدث هنا.
المهمة في الأساس هي: إضافة حالة إلى عنصر واجهة المستخدم - عداد ، في حالتنا - ودع محرك Flutter يعرف متى غيرنا الحالة لإعادة رسم عنصر واجهة المستخدم. هذا هو التعقيد الحقيقي للمشكلة (التعقيد الأساسي في شروط بروكس).
كل شيء آخر هو تعقيد عرضي. يأتي Flutter on Dart مع فئة State
جديدة تستخدم الأدوية الجنيسة وتأخذ عنصر واجهة مستخدم كمعلمة type. بعد ذلك ، يتم _MyHomePageState
فئة _MyHomePageState
، التي ترث State MyApp
... حسناً ، لا يزال بإمكانك هضمها بطريقة أو بأخرى. ولكن لماذا يتم تعريف أسلوب build()
قبل فئة الحالة ، وليس الفئة التي هي القطعة؟ Brrr ....
توجد إجابة لهذا السؤال في الأسئلة الشائعة الخاصة بـ Flutter ، ويتم اعتبار الإجابة المختصرة بتفاصيل كافية هنا - لتجنب فئة معينة من الأخطاء عند وراثة StatefulWidget
. بمعنى آخر ، يعد هذا حلًا لحل مشكلة تصميم OOP الموجه للفئة. شيك.
كيف نفعل هذا في الذهاب؟
أولاً ، أنا شخصياً أفضل عدم إنشاء كيان منفصل لـ "الدولة" - State
. بعد كل شيء ، لدينا بالفعل حالة في كل نوع معين - هذه ليست سوى حقول الهيكل. لقد أعطتنا اللغة هذا الجوهر بالفعل ، إذا جاز التعبير. إنشاء كيان آخر مماثل سوف يخلط بين المبرمج فقط.
التحدي ، بالطبع ، هو إعطاء Flutter القدرة على الاستجابة لتغيرات الحالة (هذا هو جوهر البرمجة التفاعلية ، بعد كل شيء). وإذا كان بإمكاننا "مطالبة" المطور باستخدام وظيفة خاصة ( setState()
) ، فيمكننا بنفس الطريقة أن نطلب استخدام وظيفة خاصة لمعرفة المحرك عند إعادة الرسم وعندما لا يقوم بذلك. في النهاية ، لا تتطلب كل تغييرات الحالة إعادة رسم ، وهنا سنحصل على مزيد من التحكم:
type MyHomePage struct { flutter.Core counter int }
يمكنك NeedsUpdate()
باستخدام خيارات التسمية المختلفة - أحب NeedsUpdate()
وحقيقة أن هذه خاصية عنصر واجهة مستخدم (تم الحصول عليها من flutter.Core
) ، ولكن طريقة flutter.Rerender()
العالمية تبدو جيدة أيضًا. صحيح ، أنه يعطي إحساسًا خاطئًا بأن الأداة ستعيد رسمها على الفور على الفور ، ولكنها ليست كذلك - سوف تعيد رسمها في تحديث الإطار التالي ، ويمكن أن يكون تردد استدعاء الأسلوب أعلى بكثير من تردد التقديم - ولكن يجب أن يكون محرك Flutter لدينا قادرًا بالفعل على التعامل مع هذا.
لكن الفكرة هي أننا حللنا المشكلة الضرورية دون إضافة:
- نوع جديد
- الوراثة
- قواعد خاصة للقراءة / كتابة الدولة
- طرق جديدة خاصة تجاوز
بالإضافة إلى ذلك ، API أكثر وضوحًا وأكثر قابلية للفهم - ما عليك سوى زيادة العداد (كما تفعل في أي برنامج آخر) واطلب من Flutter إعادة رسم الأداة. هذا مجرد شيء غير واضح للغاية إذا ما أطلقنا فقط setState
- وهي ليست مجرد وظيفة خاصة لتحديد الحالة ، إنها دالة تُرجع دالة (wtf؟) حيث نقوم بالفعل بعمل شيء مع الحالة. مرة أخرى ، السحر الخفي في اللغات والأطر يجعل من الصعب للغاية فهم الشفرة وقراءتها.
في حالتنا ، قمنا بحل المشكلة نفسها ، الكود أبسط وأقصر مرتين.
الدولة الحاجيات في الحاجيات الأخرى
كتواصل منطقي للموضوع ، دعنا نلقي نظرة على كيفية استخدام "عنصر واجهة المستخدم" في عنصر واجهة مستخدم آخر في Flutter:
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); }
MyHomePage
هنا عبارة عن "عنصر واجهة حالة" (به عداد) ، ونحن نقوم بإنشائه عن طريق استدعاء المنشئ MyHomePage()
أثناء الإنشاء ... انتظر ، ماذا؟
يتم استدعاء build()
لإعادة رسم عنصر واجهة المستخدم ، وربما يكون ذلك عدة مرات في الثانية. لماذا يجب علينا إنشاء عنصر واجهة مستخدم ، خاصة مع حالة ، في كل مرة أثناء التقديم؟ هذا غير منطقي
اتضح أن Flutter يستخدم هذا الفصل بين Widget
و State
أجل إخفاء إدارة التهيئة / الحالة هذه عن المبرمج (المزيد من الأشياء المخفية ، أكثر!). يقوم بإنشاء عنصر واجهة مستخدم جديد في كل مرة ، ولكن الدولة ، إذا تم إنشاؤها بالفعل ، يتم العثور عليها وإرفاقها تلقائيًا إلى عنصر واجهة المستخدم. يحدث هذا السحر بشكل غير مرئي وليس لدي أي فكرة عن كيفية عمله - عليك قراءة الكود.
أنا أعتبرها شرًا حقيقيًا في البرمجة لإخفاءها وإخفائها عن المبرمج قدر الإمكان ، مع تبريرها باستخدام بيئة العمل. أنا متأكد من أن المبرمج الإحصائي المتوسط لن يقرأ كود Flutter لفهم كيفية عمل هذا السحر ، ومن غير المرجح أن يفهم كيف وما هو مترابط.
بالنسبة إلى إصدار Go ، لا أريد بالتأكيد مثل هذه السحر الخفي ، وأفضل التهيئة الصريحة والمرئية ، حتى لو كان ذلك يعني رمزًا لا أساس له من الصحة قليلاً. يمكن تنفيذ نهج Flutter تجاه Dart ، لكني أحب Go لتقليل السحر ، وأود أن أرى نفس الفلسفة في الأطر. لذلك ، أود كتابة الكود الخاص بي لعناصر واجهة التعامل مع الحالة في شجرة عنصر واجهة المستخدم مثل هذا:
يفقد هذا الرمز إصدار Dart لأنه إذا أردت إزالة homePage
من شجرة عنصر واجهة المستخدم homePage
بشيء آخر ، فسيتعين علي إزالته في ثلاثة أماكن بدلاً من واحد. لكن بالمقابل ، نحصل على صورة كاملة عن ماذا وأين وكيف يحدث ذلك ، وأين يتم تخصيص الذاكرة ، ومن يتصل بمن ، وما إلى ذلك - الشفرة الموجودة في راحة يدك واضحة وسهلة القراءة.
بالمناسبة ، يحتوي Flutter أيضًا على شيء مثل StatefulBuilder ، والذي يضيف المزيد من السحر ويسمح لك بإنشاء تطبيقات مصغّرة مع الحالة السريعة.
DSL
الآن لنأخذ الجزء الممتع. كيف ستمثل شجرة القطعة على الذهاب؟ نريدها أن تبدو مختصرة ونظيفة وسهلة لإعادة تشكيلها وتغييرها ، وتصف العلاقات المكانية بين الحاجيات (عناصر واجهة تعامل مرئية قريبًا ، ويجب أن تكون قريبة وفي الوصف) ، وفي الوقت نفسه مرنة بدرجة كافية لوصف التعسفي رمز مثل معالجات الأحداث.
يبدو لي أن الخيار على دارت جميل وبليغ:
return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), );
كل عنصر واجهة مستخدم لديه مُنشئ يقبل المعلمات الاختيارية ، وما يجعل السجل لطيفًا هنا هي المعلمات المحددة للوظائف .
المعلمات المسماة
في حال لم تكن معتادًا على هذا المصطلح ، تُعرف معلمات الوظيفة في العديد من اللغات ب "الموضعية" ، نظرًا لأن موضعها يهم الوظيفة:
Foo(arg1, arg2, arg3)
وفي حالة المعلمات المسماة ، يتم تحديد كل شيء باسمهم في المكالمة:
Foo(name: arg1, description: arg2, size: arg3)
يؤدي ذلك إلى إضافة نص ، ولكنه يحفظ النقرات ويتحرك حول الكود ، في محاولة لفهم معنى المعلمات.
في حالة شجرة القطعة ، يلعبون دورًا رئيسيًا في قابلية القراءة. قارن الكود نفسه كما هو مذكور أعلاه ، ولكن بدون معلمات محددة:
return Scaffold( AppBar( Text(widget.title), ), Center( Column( MainAxisAlignment.center, <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', Theme.of(context).textTheme.display1, ), ], ), ), FloatingActionButton( _incrementCounter, 'Increment', Icon(Icons.add), ), );
ليس هذا. صحيح؟ ليس من الصعب فقط فهم (يجب عليك أن تضع في اعتبارك ما تعنيه كل معلمة ونوعها ، وهذا عبء إدراكي كبير) ، ولكن أيضًا لا يمنحنا الحرية في اختيار المعلمات التي نريد نقلها. على سبيل المثال ، قد لا ترغب في تطبيق FloatingActionButton
لتطبيق المواد ، لذلك لا تحدده ببساطة في المعلمات. بدون المعلمات المسماة ، سيتعين علينا إما فرض جميع الحاجيات الممكنة التي سيتم تحديدها ، أو اللجوء إلى السحر مع انعكاس لمعرفة أي الأدوات التي تم نقلها.
ونظرًا لعدم وجود حمل زائد للوظائف والمعلمات المسماة في Go ، فلن تكون هذه مهمة سهلة لـ Go.
الذهاب شجرة القطعة
الإصدار 1
دعنا نلقي نظرة فاحصة على كائن Scaffold ، وهو غلاف مناسب لتطبيق الهاتف المحمول. لديها العديد من الخصائص - appBar ، drawe ، المنزل ، bottomNavigationBar ، floatingActionBar - وهذه كلها عناصر واجهة مستخدم. عند إنشاء شجرة عنصر واجهة تعامل مستخدم ، يجب علينا في الواقع تهيئة هذا الكائن بطريقة أو بأخرى ، بتمرير خصائص عنصر واجهة المستخدم المذكورة أعلاه. حسنًا ، هذا لا يختلف كثيرًا عن التكوين والتهيئة المعتادين للكائنات.
دعونا نحاول نهج الجبين:
return flutter.NewScaffold( flutter.NewAppBar( flutter.Text("Flutter Go app", nil), ), nil, nil, flutter.NewCenter( flutter.NewColumn( flutter.MainAxisCenterAlignment, nil, []flutter.Widget{ flutter.Text("You have pushed the button this many times:", nil), flutter.Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), flutter.FloatingActionButton( flutter.NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), )
ليس أجمل رمز واجهة المستخدم ، بالتأكيد. كلمة flutter
كل مكان ويسأل. لإخفائه (في الواقع ، كان علي تسمية material
العبوة ، وليس flutter
، ولكن ليس الجوهر) ، المعلمات المجهولة غير واضحة تمامًا ، وهذه nil
مربكة في كل مكان.
الإصدار 2
نظرًا لأن معظم الكود سوف يستخدم نوعًا / وظيفة أخرى من حزمة flutter
، يمكننا استخدام تنسيق "استيراد نقطة" لاستيراد الحزمة إلى مساحة اسمنا وبالتالي "إخفاء" اسم الحزمة:
import . "github.com/flutter/flutter"
الآن بدلا من flutter.Text
. Text
يمكننا كتابة Text
فقط. عادة ما تكون هذه ممارسة سيئة ، لكننا نعمل مع الإطار ، وستكون هذه الاستيراد حرفيًا في كل سطر. من ممارستي ، هذه هي الحالة التي يكون هذا الاستيراد مقبولًا لها تمامًا - على سبيل المثال ، عند استخدام الإطار الرائع لاختبار GoConvey .
دعونا نرى كيف سيبدو الرمز:
return NewScaffold( NewAppBar( Text("Flutter Go app", nil), ), nil, nil, NewCenter( NewColumn( MainAxisCenterAlignment, nil, []Widget{ Text("You have pushed the button this many times:", nil), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), )
بالفعل أفضل ، ولكن هذه المعلمات لا شيء ومجهولة ....
الإصدار 3
دعونا نرى كيف سيبدو الرمز إذا استخدمنا الانعكاس (القدرة على فحص الكود أثناء تشغيل البرنامج) لتحليل المعلمات التي تم تمريرها. يتم استخدام هذا النهج في العديد من أطر عمل HTTP المبكرة على Go ( martini ، على سبيل المثال) ، ويُعتبر ممارسة سيئة للغاية - فهي غير آمنة ، وتفقد راحة نظام الكتابة ، وهي بطيئة نسبيًا وتضيف سحرًا إلى الشفرة - ولكن من أجل التجربة يمكنك تجربة:
return NewScaffold( NewAppBar( Text("Flutter Go app"), ), NewCenter( NewColumn( MainAxisCenterAlignment, []Widget{ Text("You have pushed the button this many times:"), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, ), )
ليست سيئة ، ويبدو أن الإصدار الأصلي من دارت ، ولكن عدم وجود المعلمات المسماة لا يزال يضر العين حقا.
الإصدار 4
دعنا نتراجع قليلاً ونسأل أنفسنا عما نحاول القيام به. لا يتعين علينا نسخ أسلوب Dart عمياء (على الرغم من أنه سيكون مكافأة لطيفة - هناك ما هو أقل من تعليم الأشخاص الذين يعرفون بالفعل Flutter on Dart). في الواقع ، نقوم فقط بإنشاء كائنات جديدة وتعيين خصائص لها.
يمكن أن تجرب بهذه الطريقة؟
scaffold := NewScaffold() scaffold.AppBar = NewAppBar(Text("Flutter Go app")) column := NewColumn() column.MainAxisAlignment = MainAxisCenterAlignment counterText := Text(fmt.Sprintf("%d", m.counter)) counterText.Style = ctx.Theme.textTheme.display1 column.Children = []Widget{ Text("You have pushed the button this many times:"), counterText, } center := NewCenter() center.Child = column scaffold.Home = center icon := NewIcon(icons.Add), fab := NewFloatingActionButton() fab.Icon = icon fab.Text = "Increment" fab.Handler = m.onPressed scaffold.FloatingActionButton = fab return scaffold
, " ", . -, – , . -, , .
, UI GTK Qt . , , Qt 5:
QGridLayout *layout = new QGridLayout(this); layout->addWidget(new QLabel(tr("Object name:")), 0, 0); layout->addWidget(m_objectName, 0, 1); layout->addWidget(new QLabel(tr("Location:")), 1, 0); m_location->setEditable(false); m_location->addItem(tr("Top")); m_location->addItem(tr("Left")); m_location->addItem(tr("Right")); m_location->addItem(tr("Bottom")); m_location->addItem(tr("Restore")); layout->addWidget(m_location, 1, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); layout->addWidget(buttonBox, 2, 0, 1, 2);
, - . , , , .
5
, – -. على سبيل المثال:
func Build() Widget { return NewScaffold(ScaffoldParams{ AppBar: NewAppBar(AppBarParams{ Title: Text(TextParams{ Text: "My Home Page", }), }), Body: NewCenter(CenterParams{ Child: NewColumn(ColumnParams{ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text(TextParams{ Text: "You have pushed the button this many times:", }), Text(TextParams{ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton( FloatingActionButtonParams{ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon(IconParams{ Icon: Icons.add, }), }, ), }) }
! , . ...Params
, . , , Go , , .
-, ...Params
, . (proposal) — " " . , FloatingActionButtonParameters{...}
{...}
. :
func Build() Widget { return NewScaffold({ AppBar: NewAppBar({ Title: Text({ Text: "My Home Page", }), }), Body: NewCenter({ Child: NewColumn({ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text({ Text: "You have pushed the button this many times:", }), Text({ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton({ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon({ Icon: Icons.add, }), }, ), }) }
Dart! .
6
, . , , , , .
, , , -, – :
button := NewButton(). WithText("Click me"). WithStyle(MyButtonStyle1)
او
button := NewButton(). Text("Click me"). Style(MyButtonStyle1)
Scaffold- :
Go – , . Dart-, :
New...()
– , . , — " , , , , , , – " .
, , 5- 6- .
"hello, world" Flutter Go:
main.go
package hello import "github.com/flutter/flutter" func main() { flutter.Run(NewMyApp()) }
app.go:
package hello import . "github.com/flutter/flutter"
home_page.go:
package hello import ( "fmt" . "github.com/flutter/flutter" )
!
الخاتمة
Vecty
, , Vecty . , , , , Vecty DOM/CSS/JS, Flutter , 120 . , Vecty , Flutter Go Vecty .
Flutter
– , . Flutter, .
Go
" Flutter Go?" "" , , , , , Flutter, , , "" . , Go .
, Go . . Go, , , -. – , , .
Go. – , .
Flutter
, Flutter , , . "/ " , Dart ( , , ). Dart, , (, ) DartVM V8, Flutter – Flutter -.
, . . , , 1.0 . , - .
game changer, Flutter , , .
UI – Flutter, .
المراجع