Flutter: إعادة استخدام منطق الحالة إما مطول أو صعب للغاية

تم إنشاؤها على ٢ مارس ٢٠٢٠  ·  420تعليقات  ·  مصدر: flutter/flutter

.

متعلق بالمناقشة حول الخطافات # 25280

TL ؛ DR: من الصعب إعادة استخدام منطق State . إما أن ينتهي بنا الأمر مع طريقة build معقدة ومتداخلة بعمق أو يتعين علينا نسخ ولصق المنطق عبر أدوات متعددة.

لا يمكن إعادة استخدام هذا المنطق من خلال mixins أو الوظائف.

مشكلة

إعادة استخدام منطق State عبر عدة StatefulWidget أمر صعب للغاية ، بمجرد أن يعتمد هذا المنطق على دورات حياة متعددة.

قد يكون المثال النموذجي هو منطق إنشاء TextEditingController (ولكن أيضًا AnimationController والرسوم المتحركة الضمنية وغير ذلك الكثير). هذا المنطق يتكون من عدة خطوات:

  • تحديد متغير على State .
    dart TextEditingController controller;
  • إنشاء وحدة التحكم (عادةً داخل initState) ، مع احتمال وجود قيمة افتراضية:
    dart <strong i="25">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • التخلص من وحدة التحكم عند التخلص من State :
    dart <strong i="30">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • فعل ما نريد باستخدام هذا المتغير داخل build .
  • (اختياري) كشف هذه الخاصية على debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

هذا ، في حد ذاته ، ليس معقدًا. تبدأ المشكلة عندما نريد توسيع نطاق هذا النهج.
قد يحتوي تطبيق Flutter النموذجي على عشرات الحقول النصية ، مما يعني تكرار هذا المنطق عدة مرات.

نسخ ولصق هذا المنطق في كل مكان "يعمل" ، ولكنه يخلق نقطة ضعف في الكود الخاص بنا:

  • قد يكون من السهل نسيان إعادة كتابة إحدى الخطوات (مثل نسيان الاتصال بـ dispose )
  • يضيف الكثير من الضوضاء في الكود

قضية Mixin

تتمثل المحاولة الأولى في تحليل هذا المنطق في استخدام مزيج:

mixin TextEditingControllerMixin<T extends StatefulWidget> on State<T> {
  TextEditingController get textEditingController => _textEditingController;
  TextEditingController _textEditingController;

  <strong i="11">@override</strong>
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

  <strong i="12">@override</strong>
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('textEditingController', textEditingController));
  }
}

ثم استخدم بهذه الطريقة:

class Example extends StatefulWidget {
  <strong i="17">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example>
    with TextEditingControllerMixin<Example> {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return TextField(
      controller: textEditingController,
    );
  }
}

لكن هذا له عيوب مختلفة:

  • يمكن استخدام المزيج مرة واحدة فقط لكل فصل. إذا كان StatefulWidget يحتاج إلى عدة TextEditingController ، فلا يمكننا استخدام نهج mixin بعد الآن.

  • قد تتعارض "الحالة" التي أعلنها المزيج مع mixin آخر أو مع State نفسه.
    وبشكل أكثر تحديدًا ، إذا أعلن اثنان من mixins أن عضوًا يستخدم نفس الاسم ، فسيحدث تعارض.
    السيناريو الأسوأ ، إذا كان للأعضاء المتضاربين نفس النوع ، فسوف يفشل هذا بصمت.

هذا يجعل الخلطات غير مثالية وخطيرة للغاية بحيث لا يمكن أن تكون حلاً حقيقيًا.

استخدام نمط "builder"

قد يكون الحل الآخر هو استخدام نفس النمط مثل StreamBuilder & co.

يمكننا إنشاء عنصر واجهة مستخدم TextEditingControllerBuilder ، والذي يدير وحدة التحكم هذه. ثم يمكن لطريقتنا build استخدامها بحرية.

عادة ما يتم تنفيذ هذه الأداة بهذه الطريقة:

class TextEditingControllerBuilder extends StatefulWidget {
  const TextEditingControllerBuilder({Key key, this.builder}) : super(key: key);

  final Widget Function(BuildContext, TextEditingController) builder;

  <strong i="12">@override</strong>
  _TextEditingControllerBuilderState createState() =>
      _TextEditingControllerBuilderState();
}

class _TextEditingControllerBuilderState
    extends State<TextEditingControllerBuilder> {
  TextEditingController textEditingController;

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(
        DiagnosticsProperty('textEditingController', textEditingController));
  }

  <strong i="14">@override</strong>
  void dispose() {
    textEditingController.dispose();
    super.dispose();
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    return widget.builder(context, textEditingController);
  }
}

ثم تستخدم على هذا النحو:

class Example extends StatelessWidget {
  <strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller) {
        return TextField(
          controller: controller,
        );
      },
    );
  }
}

هذا يحل المشاكل التي تواجهها مع mixins. لكنها تخلق قضايا أخرى.

  • الاستخدام مطول للغاية. هذا فعال 4 أسطر من التعليمات البرمجية + مستويين من المسافة البادئة لإعلان متغير واحد.
    هذا أسوأ إذا أردنا استخدامه عدة مرات. بينما يمكننا إنشاء TextEditingControllerBuilder داخل آخر مرة واحدة ، فإن هذا يقلل بشكل كبير من قابلية قراءة الكود:

    <strong i="28">@override</strong>
    Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller1) {
        return TextEditingControllerBuilder(
          builder: (context, controller2) {
            return Column(
              children: <Widget>[
                TextField(controller: controller1),
                TextField(controller: controller2),
              ],
            );
          },
        );
      },
    );
    }
    

    هذا رمز ذو مسافة بادئة فقط للإعلان عن متغيرين.

  • هذا يضيف بعض النفقات العامة لأن لدينا مثيل إضافي State و Element مثيل.

  • من الصعب استخدام TextEditingController خارج build .
    إذا كنا نريد دورات حياة State لإجراء بعض العمليات على وحدات التحكم هذه ، فسنحتاج إلى GlobalKey للوصول إليها. على سبيل المثال:

    class Example extends StatefulWidget {
    <strong i="43">@override</strong>
    _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
    final textEditingControllerKey =
        GlobalKey<_TextEditingControllerBuilderState>();
    
    <strong i="44">@override</strong>
    void didUpdateWidget(Example oldWidget) {
      super.didUpdateWidget(oldWidget);
    
      if (something) {
        textEditingControllerKey.currentState.textEditingController.clear();
      }
    }
    
    <strong i="45">@override</strong>
    Widget build(BuildContext context) {
      return TextEditingControllerBuilder(
        key: textEditingControllerKey,
        builder: (context, controller) {
          return TextField(controller: controller);
        },
      );
    }
    }
    
P5 crowd framework passed first triage proposal new feature

التعليق الأكثر فائدة

سأضيف بعض الأفكار من منظور React.
عفوا إذا لم تكن ذات صلة لكنني أردت أن أشرح بإيجاز كيف نفكر في الخطافات.

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

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

حسنًا ، إنها تعمل مع حالة مخفية ضمنية بداخلها ، هذا يبدو سيئًا؟ لكن في React ، كذلك المكونات بشكل عام. هذا هو بيت القصيد من المكونات. إنها وظائف لها عمر مرتبط بها (والذي يتوافق مع موضع في شجرة واجهة المستخدم). السبب في أن المكونات نفسها ليست أداة مساعدة فيما يتعلق بالحالة هو أنك لا تستدعيها فقط من رمز عشوائي. يمكنك الاتصال بهم من مكونات أخرى. لذا فإن حياتهم منطقية لأنك تظل في سياق رمز واجهة المستخدم.

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

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

من الناحية المفاهيمية ، لا ننظر إلى مكالمات الخطاف على أنها مكالمات دالة عادية. مثل useState() يكون أكثر من use State() إذا كان لدينا بناء الجملة. ستكون ميزة لغوية. يمكنك نمذجة شيء مثل الخطافات ذات التأثيرات الجبرية باللغات التي لها تتبع تأثير. وبهذا المعنى ، ستكون وظائف عادية ، لكن حقيقة أنها "تستخدم" الدولة ستكون جزءًا من نوع التوقيع الخاص بها. ثم يمكنك التفكير في React نفسها على أنها "معالج" لهذا التأثير. على أي حال ، هذا نظري للغاية لكنني أردت أن أشير إلى الفن السابق فيما يتعلق بنموذج البرمجة.

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

بالنسبة لما يسمحون به ، أعتقد أن الميزة الأساسية هي القدرة على تغليف الحالة + المنطق الفعال ، ثم ربطها معًا كما تفعل مع تكوين الوظيفة العادية. نظرًا لأن العناصر الأولية مصممة للتكوين ، يمكنك أخذ بعض مخرجات الخطاف مثل useState() ، وتمريرها كمدخل إلى cusom useGesture(state) ، ثم تمرير ذلك كمدخل إلى عدة useSpring(gesture) مخصصة توضيحيًا صغيرًا لشيء كهذا ، ومقال حيث ألخص بإيجاز ماهية الخطافات.

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

لا أعرف ما إذا كان هذا مفيدًا على الإطلاق ، ولكن آمل أن يلقي بعض المنظور على نموذج البرمجة.
سعيد للإجابة على الأسئلة الأخرى.

ال 420 كومينتر

تضمين التغريدة
حسب الطلب ، هذه هي التفاصيل الكاملة حول المشكلات التي يتم حلها بواسطة الخطافات.

أنا قلق من أن أي محاولة لتسهيل ذلك داخل إطار العمل ستخفي بالفعل التعقيد الذي يجب على المستخدمين التفكير فيه.

يبدو أن بعضًا من هذا يمكن تحسينه لمؤلفي المكتبات إذا قمنا بكتابة الفصول الدراسية التي تحتاج إلى التخلص منها بنوع من abstract class Disposable . في مثل هذه الحالة ، يجب أن تكون قادرًا على كتابة فصل أبسط مثل هذا بسهولة أكبر إذا كنت مائلاً جدًا:

class AutomaticDisposingState<T> extends State<T> {
  List<Disposable> _disposables;

  void addDisposable(Disposable disposable) {
    assert(!_disposables.contains(disposable));
    _disposables.add(disposable);
  }

  <strong i="8">@override</strong>
  void dispose() {
    for (final Disposable disposable in _disposables)
      disposable.dispose();
    super.dispose();
  }
}

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

<strong i="12">@override</strong>
void initState() {
  super.initState();
  controller = TextEditingController(text: 'Hello world');
  addDisposable(controller);
  addProperty('controller', controller);
}

هل نفتقد فقط توفير معلومات الكتابة هذه للفئات التي يمكن التخلص منها؟

أنا قلق من أن أي محاولة لتسهيل ذلك داخل إطار العمل ستخفي بالفعل التعقيد الذي يجب على المستخدمين التفكير فيه.

تخفي الأدوات التعقيد الذي يجب على المستخدمين التفكير فيه.
لست متأكدًا من أن هذه مشكلة حقًا.

في النهاية ، الأمر متروك للمستخدمين لتحليله بالشكل الذي يريدونه.


المشكلة ليست فقط حول المستهلكات.

هذا ينسى جزء التحديث من المشكلة. يمكن أن يعتمد منطق المرحلة أيضًا على دورات الحياة مثل didChangeDependencies و didUpdateWidget.

بعض الأمثلة الملموسة:

  • SingleTickerProviderStateMixin الذي له منطق داخل didChangeDependencies .
  • AutomaticKeepAliveClientMixin ، والذي يعتمد على super.build(context)

هناك العديد من الأمثلة في إطار العمل حيث نريد إعادة استخدام منطق الحالة:

  • StreamBuilder
  • TweenAnimationBuilder
    ...

هذه ليست سوى طريقة لإعادة استخدام الحالة بآلية تحديث.

لكنهم يعانون من نفس المشكلة التي ذكرها "البناء".

هذا يسبب العديد من المشاكل.
على سبيل المثال ، إحدى أكثر المشكلات شيوعًا في Stackoverflow هي محاولة الأشخاص استخدام StreamBuilder للتأثيرات الجانبية ، مثل "دفع الطريق عند التغيير".

وفي النهاية فإن الحل الوحيد لهم هو "إخراج" StreamBuilder.
وهذا ينطوي:

  • تحويل القطعة إلى مصغر
  • استمع يدويًا إلى الدفق في initState + didUpdateWidget + didChangeDependencies
  • إلغاء الاشتراك السابق في didChangeDependencies / didUpdateWidget عندما يتغير الدفق
  • إلغاء الاشتراك عند التخلص منها

هذا - الكثير من العمل - وهو غير قابل لإعادة الاستخدام بشكل فعال.

مشكلة

إعادة استخدام منطق State عبر عدة StatefulWidget أمر صعب للغاية ، بمجرد أن يعتمد هذا المنطق على دورات حياة متعددة.

قد يكون المثال النموذجي هو منطق إنشاء TextEditingController (ولكن أيضًا AnimationController والرسوم المتحركة الضمنية وغير ذلك الكثير). هذا المنطق يتكون من عدة خطوات:

  • تحديد متغير على State .
    dart TextEditingController controller;
  • إنشاء وحدة التحكم (عادةً داخل initState) ، مع احتمال وجود قيمة افتراضية:
    dart <strong i="19">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • التخلص من وحدة التحكم عند التخلص من State :
    dart <strong i="24">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • فعل ما نريد باستخدام هذا المتغير داخل build .
  • (اختياري) كشف هذه الخاصية على debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

هذا ، في حد ذاته ، ليس معقدًا. تبدأ المشكلة عندما نريد توسيع نطاق هذا النهج.
قد يحتوي تطبيق Flutter النموذجي على عشرات الحقول النصية ، مما يعني تكرار هذا المنطق عدة مرات.

نسخ ولصق هذا المنطق في كل مكان "يعمل" ، ولكنه يخلق نقطة ضعف في الكود الخاص بنا:

  • قد يكون من السهل نسيان إعادة كتابة إحدى الخطوات (مثل نسيان الاتصال بـ dispose )
  • يضيف الكثير من الضوضاء في الكود

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

أوافق على أن mixin لكل نوع ملكية لا يعمل. أوافق على أن نمط المنشئ ليس جيدًا (يستخدم حرفياً نفس عدد الأسطر مثل أسوأ سيناريو موصوف أعلاه).

مع NNBD (على وجه التحديد مع late final بحيث يمكن للمبدئين الرجوع إلى this ) سنتمكن من القيام بشيء مثل هذا:

typedef Initializer<T> = T Function();
typedef Disposer<T> = void Function(T value);

mixin StateHelper<T extends StatefulWidget> on State<T> {
  bool _active = false;
  List<Property<Object>> _properties = <Property<Object>>[];

  <strong i="8">@protected</strong>
  void registerProperty<T>(Property<T> property) {
    assert(T != Object);
    assert(T != dynamic);
    assert(!_properties.contains(property));
    _properties.add(property);
    if (_active)
      property._initState();
  }

  <strong i="9">@override</strong>
  void initState() {
    _active = true;
    super.initState();
    for (Property<Object> property in _properties)
      property._initState();
  }

  <strong i="10">@override</strong>
  void dispose() {
    for (Property<Object> property in _properties)
      property._dispose();
    super.dispose();
    _active = false;
  }

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    for (Property<Object> property in _properties)
      property._debugFillProperties(properties);
  }
}

class Property<T> {
  Property(this.owner, this.initializer, this.disposer, [ this.debugName ]) {
    owner.registerProperty(this);
  }

  final StateHelper<StatefulWidget> owner;
  final Initializer<T> initializer;
  final Disposer<T> disposer;
  final String debugName;

  T value;

  void _initState() {
    if (initializer != null)
      value = initializer();
  }

  void _dispose() {
    if (disposer != null)
      disposer(value);
    value = null;
  }

  void _debugFillProperties(DiagnosticPropertiesBuilder properties) {
    properties.add(DiagnosticsProperty(debugName ?? '$T property', value));
  }
}

ستستخدمه على النحو التالي:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  <strong i="14">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  late final Property<int> _counter = Property<int>(this, null, null);
  late final Property<TextEditingController> _text = Property<TextEditingController>(this,
    () => TextEditingController(text: 'button'),
    (TextEditingController value) => value.dispose(),
  );

  void _incrementCounter() {
    setState(() {
      _counter.value += 1;
    });
  }

  <strong i="15">@override</strong>
  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 ${_text.value.text} this many times:',
            ),
            Text(
              '${_counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
            TextField(
              controller: _text.value,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

لا يبدو أنه يجعل الأمور أفضل حقًا. لا يزال هناك أربعة أسطر.

تخفي الأدوات التعقيد الذي يجب على المستخدمين التفكير فيه.

ماذا يخفون؟

لا تكمن المشكلة في عدد السطور ، بل في ماهية هذه السطور.

قد يكون StreamBuilder عدد الأسطر تقريبًا مثل stream.listen + setState + subscription.close .
لكن كتابة StreamBuilder يمكن أن تتم بدون أي تفكير ، إذا جاز التعبير.
لا يوجد خطأ محتمل في هذه العملية. ما عليك سوى "تمرير الدفق وإنشاء عناصر واجهة منه".

في حين أن كتابة الكود يدويًا تتضمن الكثير من الأفكار:

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

ماذا يخفون؟

  • يقوم FutureBuilder / StreamBuilder بإخفاء آلية الاستماع ويتتبع ما هي اللقطة الحالية.
    منطق التبديل بين Future معقد إلى حد ما أيضًا ، مع الأخذ في الاعتبار أنه لا يحتوي على subscription.close() .
  • يُخفي AnimatedContainer منطق إنشاء وسيط بين القيم السابقة والقيم الجديدة.
  • يخفي Listview منطق "تحميل أداة كما تظهر"

التطبيقات بشكل عام لا داعي للقلق بشأن إضافة الحالة إلى خصائص تصحيح الأخطاء

لا يفعلون ذلك ، لأنهم لا يريدون التعامل مع تعقيد الحفاظ على طريقة debugFillProperties.
ولكن إذا قلنا للمطورين "هل ترغب في أن تكون جميع المعلمات وخصائص الحالة متاحة في أداة تطوير Flutter؟" أنا متأكد من أنهم سيقولون نعم

لقد أعرب لي العديد من الأشخاص عن رغبتهم في الحصول على معادل حقيقي لأداة تطوير React. أداة تطوير Flutter ليست موجودة بعد.
في React ، يمكننا رؤية كل حالة عنصر واجهة مستخدم + معلماته ، وتعديله دون القيام بأي شيء.

وبالمثل ، فوجئ الناس تمامًا عندما أخبرتهم أنه عند استخدام provider + بعض الحزم الأخرى الخاصة بي ، تكون حالة التطبيق بالكامل بشكل افتراضي مرئية لهم ، دون الحاجة إلى فعل أي شيء ( modulo هذا الخطأ devtool المزعج )

يجب أن أعترف أنني لست من كبار المعجبين بـ FutureBuilder ، فهو يسبب الكثير من الأخطاء لأن الناس لا يفكرون في وقت إطلاق المستقبل. أعتقد أنه لن يكون من غير المعقول أن نتخلى عن دعمنا لها. أعتقد أن StreamBuilder على ما يرام ولكن أعتقد بعد ذلك أن Streams نفسها معقدة للغاية (كما ذكرت في تعليقك أعلاه) لذا ...

لماذا يجب على شخص ما التفكير في تعقيد إنشاء المراهقين؟

لا يخفي ListView حقًا منطق تركيب عنصر واجهة المستخدم كما يظهر ؛ إنه جزء كبير من واجهة برمجة التطبيقات.

لا تكمن المشكلة في عدد السطور ، بل في ماهية هذه السطور.

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

سأتفق معك في أن FutureBuilder يمثل مشكلة.

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

لماذا يجب على شخص ما التفكير في تعقيد إنشاء المراهقين؟

لا يخفي ListView حقًا منطق تركيب عنصر واجهة المستخدم كما يظهر ؛ إنه جزء كبير من واجهة برمجة التطبيقات.

نحن نتفق على ذلك. كانت وجهة نظري أنه لا يمكننا انتقاد شيء مثل الخطافات باستخدام "منطق يخفي" ، لأن ما تفعله الخطافات يعادل تمامًا ما يفعله TweenAnimationBuilder / AnimatedContainer / ....
المنطق غير مخفي

في النهاية ، أعتقد أن الرسوم المتحركة هي مقارنة جيدة. الرسوم المتحركة لديها هذا المفهوم الضمني مقابل الصريح.
الرسوم المتحركة الضمنية محبوبة بسبب بساطتها وقابليتها للتركيب وقابليتها للقراءة.
الرسوم المتحركة الصريحة أكثر مرونة ولكنها أكثر تعقيدًا.

عندما نترجم هذا المفهوم للاستماع إلى التدفقات ، يكون StreamBuilder _ استماعًا ضمنيًا_ ، بينما stream.listen _explicit_.

وبشكل أكثر تحديدًا ، مع StreamBuilder يمكنك _لا تنسى التعامل مع السيناريو الذي يتغير فيه البث ، أو تنسى إغلاق الاشتراك.
يمكنك أيضًا الجمع بين عدة StreamBuilder معًا

stream.listen أكثر تقدمًا قليلًا وأكثر عرضة للخطأ.

البناة أقوياء في تبسيط التطبيق.
ولكن كما اتفقنا سابقًا ، فإن نموذج Builder ليس مثاليًا. إنه مطول في الكتابة والاستخدام.
هذه المشكلة ، وما يحلها الخطاف ، تتعلق بصيغة بديلة لـ * البناة

على سبيل المثال ، flutter_hooks له ما يعادل FutureBuilder و StreamBuilder :

Widget build(context) {
  final AsyncSnapshot<T> snapshot = useStream(stream);
}

في استمرار ، يمكن تمثيل AnimatedContainer وعلى حد سواء بـ useAnimatedSize / useAnimatedDecoractedBox / ... بحيث يكون لدينا:

double opacity;

Widget build(context) {
  final double animatedOpacity = useAnimatedDouble(opacity, duration: Duration(milliseconds: 200));
  return Opacity(
    opacity: animatedOpacity,
    child: ...,
  );
}

كانت وجهة نظري أنه لا يمكننا انتقاد شيء مثل الخطافات بعبارة "يخفي المنطق" ،

هذه ليست الحجة. الحجة هي "يخفي المنطق الذي يجب أن يفكر فيه المطورون ".

هل لديك مثال على هذا المنطق الذي يجب أن يفكر فيه المطورون؟

مثل ، من يملك TextEditingController (من يقوم بإنشائه ، ومن يتصرف فيه).

مثل هذا الرمز؟

Widget build(context) {
  final controller = useTextEditingController();
  final focusNode = useFocusNode();
}

الخطاف يخلقه ويتخلص منه.

لست متأكدًا مما هو غير واضح بشأن هذا.

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

يبدو أن سبب حجتك هو عدم فهم ما تفعله الخطافات بدلاً من مشكلة حقيقية.
هذه الأسئلة لها إجابة محددة بوضوح تتوافق مع جميع الخطافات:

ليس لدي أي فكرة عن دورة حياة وحدة التحكم باستخدام هذا الرمز

ولا يجب عليك التفكير في الأمر. لم يعد من مسؤولية المطور.

هل يستمر حتى نهاية النطاق المعجمي؟ عمر الدولة

عمر الدولة

من يمتلكه؟

الخطاف يمتلك جهاز التحكم. إنها جزء من واجهة برمجة التطبيقات لـ useTextEditingController أنها تمتلك وحدة التحكم.
ينطبق هذا على useFocusNode ، useScrollController ، useAnimationController ، ...

بطريقة ما ، تنطبق هذه الأسئلة على StreamBuilder :

  • لا يتعين علينا التفكير في دورات حياة الاشتراك في StreamSubscription
  • يستمر الاشتراك مدى الحياة للدولة
  • StreamBuilder يمتلك StreamSubscription

بشكل عام ، يمكنك التفكير في:

final value = useX(argument);

كمكافئ صارم لـ:

XBuilder(
  argument: argument,
  builder: (context, value) {

  },
);

لديهم نفس القواعد ونفس السلوك.

لم يعد من مسؤولية المطور

أعتقد أن هذا هو الخلاف الأساسي هنا. إن وجود واجهة برمجة تطبيقات تشبه الوظيفة تقوم بإرجاع قيمة لها فترة حياة محددة غير واضحة ، IMHO ، تختلف اختلافًا كبيرًا عن واجهة برمجة التطبيقات (API) القائمة على تمرير هذه القيمة إلى الإغلاق.

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

تضمين التغريدة
لا أعتقد أن ما قالتهrousselGit هو أنهما نفس الشيء ولكن فقط أن لديهم "نفس القواعد ونفس السلوك" فيما يتعلق بدورة الحياة؟ صيح؟

لكنهم لا يحلون نفس المشاكل.

ربما أكون مخطئًا هنا ولكن في الخريف الماضي عند تجربة الرفرفة ، أعتقد أنه إذا كنت سأحتاج إلى ثلاثة من هؤلاء البناة في أداة واحدة ، لكان هناك الكثير من التعشيش. مقارنة بثلاثة خطافات (ثلاثة خطوط).
أيضا. الخطافات قابلة للتركيب ، لذا إذا كنت بحاجة إلى مشاركة منطق الحالة المكون من خطافات متعددة ، فيمكنك إنشاء خطاف جديد يستخدم خطافات أخرى وبعض المنطق الإضافي واستخدام الخطاف الجديد فقط.

مثل هذه الأشياء مثل مشاركة منطق الحالة بسهولة بين الأدوات كانت شيئًا فقدته عند تجربة خريف الرفرفة لعام 2019.

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

أنا بالتأكيد لا أقترح استخدام نهج البناء ، كما يذكر OP ، الذي يحتوي على جميع أنواع المشاكل. ما أقترحه هو فقط استخدام initState / dispose. لا أفهم حقًا لماذا هذه مشكلة.

أشعر بالفضول حيال شعور الناس تجاه الكود في https://github.com/flutter/flutter/issues/51752#issuecomment -664787791. لا أعتقد أنه أفضل من initState / التخلص ، ولكن إذا كان الناس يحبون الخطافات ، فهل يحبون ذلك أيضًا؟ هل الخطافات أفضل؟ أسوأ؟

Hixie Hooks من الجيد استخدامها لأنها useAnimationController ، فلا داعي للتفكير في initState والتخلص منها بعد الآن. يزيل المسؤولية عن المطور. لا داعي للقلق بشأن ما إذا كنت قد تخلصت من كل وحدة تحكم في الرسوم المتحركة أنشأتها.

initState و dispose جيدان لشيء واحد ولكن تخيل الحاجة إلى تتبع أنواع متعددة ومتباينة من الحالات. تؤلف الخطافات بناءً على الوحدة المنطقية للتجريد بدلاً من نشرها في دورة حياة الفصل.

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

سأضيف بعض الأفكار من منظور React.
عفوا إذا لم تكن ذات صلة لكنني أردت أن أشرح بإيجاز كيف نفكر في الخطافات.

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

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

حسنًا ، إنها تعمل مع حالة مخفية ضمنية بداخلها ، هذا يبدو سيئًا؟ لكن في React ، كذلك المكونات بشكل عام. هذا هو بيت القصيد من المكونات. إنها وظائف لها عمر مرتبط بها (والذي يتوافق مع موضع في شجرة واجهة المستخدم). السبب في أن المكونات نفسها ليست أداة مساعدة فيما يتعلق بالحالة هو أنك لا تستدعيها فقط من رمز عشوائي. يمكنك الاتصال بهم من مكونات أخرى. لذا فإن حياتهم منطقية لأنك تظل في سياق رمز واجهة المستخدم.

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

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

من الناحية المفاهيمية ، لا ننظر إلى مكالمات الخطاف على أنها مكالمات دالة عادية. مثل useState() يكون أكثر من use State() إذا كان لدينا بناء الجملة. ستكون ميزة لغوية. يمكنك نمذجة شيء مثل الخطافات ذات التأثيرات الجبرية باللغات التي لها تتبع تأثير. وبهذا المعنى ، ستكون وظائف عادية ، لكن حقيقة أنها "تستخدم" الدولة ستكون جزءًا من نوع التوقيع الخاص بها. ثم يمكنك التفكير في React نفسها على أنها "معالج" لهذا التأثير. على أي حال ، هذا نظري للغاية لكنني أردت أن أشير إلى الفن السابق فيما يتعلق بنموذج البرمجة.

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

بالنسبة لما يسمحون به ، أعتقد أن الميزة الأساسية هي القدرة على تغليف الحالة + المنطق الفعال ، ثم ربطها معًا كما تفعل مع تكوين الوظيفة العادية. نظرًا لأن العناصر الأولية مصممة للتكوين ، يمكنك أخذ بعض مخرجات الخطاف مثل useState() ، وتمريرها كمدخل إلى cusom useGesture(state) ، ثم تمرير ذلك كمدخل إلى عدة useSpring(gesture) مخصصة توضيحيًا صغيرًا لشيء كهذا ، ومقال حيث ألخص بإيجاز ماهية الخطافات.

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

لا أعرف ما إذا كان هذا مفيدًا على الإطلاق ، ولكن آمل أن يلقي بعض المنظور على نموذج البرمجة.
سعيد للإجابة على الأسئلة الأخرى.

أنا بالتأكيد لا أقترح استخدام نهج البناء ، كما يذكر OP ، الذي يحتوي على جميع أنواع المشاكل. ما أقترحه هو فقط استخدام initState / dispose. لا أفهم حقًا لماذا هذه مشكلة.

أشعر بالفضول حيال شعور الناس تجاه الكود في # 51752 (تعليق) . لا أعتقد أنه أفضل من initState / التخلص ، ولكن إذا كان الناس يحبون الخطافات ، فهل يحبون ذلك أيضًا؟ هل الخطافات أفضل؟ أسوأ؟

تجعل الكلمة الرئيسية late الأمور أفضل ، لكنها لا تزال تعاني من بعض المشكلات:

قد يكون هذا Property مفيدًا للحالات القائمة بذاتها أو التي لا تعتمد على معلمات يمكن أن تتغير بمرور الوقت. ولكن قد يصعب استخدامه عندما تكون في موقف مختلف.
بتعبير أدق ، فإنه يفتقر إلى جزء "التحديث".

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

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

مثال على ذلك هو useMemo ، وهو عبارة عن ربط يقوم بتخزين مثيل من الكائن مؤقتًا.
بالاقتران مع المفاتيح ، يمكننا استخدام useMemo للحصول على جلب بيانات ضمنية.
على سبيل المثال ، قد تتلقى عنصر واجهة المستخدم لدينا معرف رسالة - والذي نستخدمه بعد ذلك لجلب تفاصيل الرسالة. لكن معرف الرسالة هذا قد يتغير بمرور الوقت ، لذلك قد نحتاج إلى إعادة جلب التفاصيل.

مع useMemo ، قد يبدو هذا كما يلي:

String messageId;

Widget build(context) {
  final Future<Message> message = useMemo(() => fetchMessage(messageId), [messageId]);

}

في هذه الحالة ، حتى إذا تم استدعاء طريقة الإنشاء مرة أخرى 10 مرات ، طالما لم يتغير messageId ، فلن يتم تنفيذ جلب البيانات مرة أخرى.
ولكن عندما يتغير messageId ، يتم إنشاء Future .


تجدر الإشارة إلى أنني لا أعتقد أن flutter_hooks في حالته الحالية قد تم تنقيته من أجل Dart. تطبيقي هو أكثر من POC من بنية كاملة.
لكنني أعتقد أن لدينا مشكلة في إعادة استخدام التعليمات البرمجية لـ StatefulWidgets.

لم أتذكر أين ، لكنني أتذكر اقتراح أن الخطافات في العالم المثالي ستكون مولد وظيفة مخصصة ، بجانب async* & sync* ، والذي قد يكون مشابهًا لما يقترحه Dan بـ use State بدلاً من useState

تضمين التغريدة

أريد أن أؤكد أن هذا لا يتعلق بتقليل النموذج المعياري ولكن يتعلق بالقدرة على تكوين خطوط أنابيب ديناميكيًا للمنطق المغلف المصحوب بالحالة.

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

لا ، إنه محق ، ربما تكون صياغتي محيرة.
كما ذكرت سابقًا ، لا يتعلق الأمر بعدد سطور الكود ، ولكن سطور الكود نفسها.

هذا عن الحالة إلى عوامل.

هذا الخطأ واضح للغاية بشأن المشكلة المتمثلة في "إعادة استخدام منطق الحالة أمر مطوّل / صعب للغاية" ، ويتعلق الأمر بكيفية وجود الكثير من التعليمات البرمجية في الولاية عندما يكون لديك خاصية تحتاج إلى رمز لإعلانها ، في initState ، في التخلص منها وفي debugFillProperties. إذا كانت المشكلة التي تهتم بها شيئًا مختلفًا ، فأنا أوصي بتسجيل خطأ جديد يصف هذه المشكلة.

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

أعتقد أنك تسيء فهم ما قلته في ذلك الوقت.

لا تكمن المشكلة بأي حال من الأحوال في إمكانية إعادة الاستخدام.

Boilerplate هو نتيجة لمشكلة إعادة الاستخدام ، وليس السبب

ما تصفه هذه المشكلة هو:

قد نرغب في إعادة استخدام / تكوين منطق الحالة. لكن الخيارات المتاحة هي إما mixins أو Builders أو عدم إعادة استخدامها - وكلها لها مشكلاتها الخاصة.

قد تكون مشكلات الخيارات الحالية مرتبطة بالنمط المعياري ، لكن المشكلة التي نحاول حلها ليست كذلك.
في حين أن الحد من الصيغة المعيارية للبناة هو مسار واحد (وهو ما تفعله الخطافات) ، فقد يكون هناك مسار مختلف.

على سبيل المثال ، هناك شيء أردت اقتراحه لفترة من الوقت وهو إضافة طرق مثل:

context.onDidChangeDependencies(() {

});
context.onDispose(() {

});

لكن هذه لها مشكلاتها الخاصة ولا تحل المشكلة بالكامل ، لذلك لم أفعل.

rousselGit ، لا تتردد في تعديل بيان المشكلة الأصلي في الجزء العلوي هنا لتعكس المشكلة بشكل أفضل. لا تتردد أيضًا في إنشاء مستند تصميم: https://flutter.dev/docs/resources/design-docs يمكننا التكرار معًا (مرة أخرى ، كما يقترح Hixie ، التركيز في الوقت الحالي على

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

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

قبل بضعة أسابيع ، تم تعييني لمناقشة الهندسة المعمارية حول تطبيق Flutter موجود. ربما كان بالضبط ما هو مذكور هنا:

  • لديهم بعض المنطق الذي يجب إعادة استخدامه في عناصر واجهة مستخدم متعددة (التعامل مع حالات التحميل / وضع علامة على "الرسائل" كمقروءة عندما تصبح بعض الأدوات مرئية / ...)
  • لقد حاولوا استخدام mixins ، مما تسبب في عيوب معمارية كبيرة.
  • لقد حاولوا أيضًا التعامل يدويًا مع "الإنشاء / التحديث / التخلص" من خلال إعادة كتابة هذا المنطق في مواقع متعددة ، ولكنه تسبب في حدوث أخطاء.
    في بعض الأماكن نسوا إغلاق الاشتراكات. في حالات أخرى ، لم يتعاملوا مع السيناريو الذي يتغير فيه مثيل stream
  • وضع علامة على "الرسائل" كمقروءة عندما تصبح بعض الأدوات مرئية

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

https://github.com/jocosocial/rainbowmonkey/blob/master/lib/src/views/forums.dart

هل لديك أمثلة على تطبيقات فعلية يمكنني دراستها لمعرفة المشكلة قيد التنفيذ؟

(راجع للشغل ، بشكل عام ، أوصي بشدة بعدم استخدام التدفقات على الإطلاق. أعتقد أنها تزيد الأمور سوءًا بشكل عام.)

(راجع للشغل ، بشكل عام ، أوصي بشدة بعدم استخدام التدفقات على الإطلاق. أعتقد أنها تزيد الأمور سوءًا بشكل عام.)

(أوافق تمامًا. ولكن المجتمع لديه حاليًا رد فعل معاكس. ربما يساعد استخراج ChangeNotifier / Listenable / ValueNotifier من Flutter إلى حزمة رسمية)

هل لديك أمثلة على تطبيقات فعلية يمكنني دراستها لمعرفة المشكلة قيد التنفيذ؟

للأسف لا. يمكنني فقط مشاركة التجربة التي مررت بها أثناء مساعدة الآخرين. ليس لدي تطبيق في متناول اليد.

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

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

قد يكون أفضل مثال على ذلك هو السحب للتحديث.

في نموذج سحب للتحديث ، سنحتاج إلى:

  • في البناء الأول ، تعامل مع حالات التحميل / الخطأ
  • عند التحديث:

    • إذا كانت الشاشة في حالة خطأ ، اعرض شاشة التحميل مرة أخرى

    • إذا تم إجراء التحديث أثناء التحميل ، فقم بإلغاء طلبات HTTP المعلقة

    • إذا أظهرت الشاشة بعض البيانات:

    • استمر في عرض البيانات أثناء تحميل الحالة الجديدة

    • في حالة فشل التحديث ، استمر في إظهار البيانات التي تم الحصول عليها مسبقًا وإظهار شريط الوجبات الخفيفة مع الخطأ

    • إذا انبثق المستخدم من الشاشة وأعاد دخوله مرة أخرى أثناء انتظار التحديث ، فقم بإظهار شاشة التحميل

    • تأكد من أن RefreshIndicator تشير إلى أنه مرئي أثناء تعليق التحديث

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

ChangeNotifier + provider + StatefulWidget سيواجه الكثير من الصعوبات في تحليل هذا المنطق.

في حين أن تجاربي الأخيرة (التي تعتمد على الثبات وتعتمد على flutter_hooks ) تدعم الطيف بالكامل خارج الصندوق:

final productsProvider = FutureProvider<List<Product>>.autoDispose((ref) async {
  final cancelToken = CancelToken();
  ref.onDispose(cancelToken.cancel);

  return await repository.fetchProducts(cancelToken: cancelToken);
});

// ...

Widget build(context) {
  // Listens to the Future created by productsProvider and handles all the refresh logic
  AsyncValue<List<Product>> products = useRefreshProvider(
    productsProvider,
    // TODO consider making a custom hook to encaplusate the snackbar logic
    onErrorAfterRefresh: (err, stack) => Scaffold.of(context).showSnackBar(...),
  );

  return RefreshIndicator(
    onRefresh: () => context.refresh(productsProvider),
    child: products.when(
      loading: () {
        return const SingleChildScrollView(
          physics: AlwaysScrollableScrollPhysics(),
          child: CircularProgressIndicator(),
        );
      },
      error: (err, stack) {
        return SingleChildScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          child: Text('Oops, something unexpected happened\n$err'),
        );
      },
      data: (products) {
        return ListView.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            return ProductItem(products[index]);
          },
        );
      },
    ),
  );
}

هذا المنطق قائم بذاته تمامًا. يمكن إعادة استخدامه مع أي مورد داخل أي شاشات.

وإذا أرادت شاشة واحدة تحديث موارد متعددة في وقت واحد ، فيمكننا القيام بما يلي:

AsyncValue<First> first = useRefreshProvider(
  firstProvider,
  onErrorAfterRefresh: ...
);
AsyncValue<Second> second = useRefreshProvider(
  secondProvider,
  onErrorAfterRefresh: ...
);

return RefreshIndicator(
  onRefresh: () {
     return Future.wait([context.refesh(firstProvider), context.refresh(secondProvider)]);
  }
  ...
)

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

ليس من مسؤولية الحالة المحيطة تحديد كيفية عرض الخطأ مقابل التحميل مقابل البيانات

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

وهذا أقل قابلية لإعادة الاستخدام.
إذا تم تعريف منطق العرض بالكامل في عناصر واجهة المستخدم بدلاً من الحالة المحيطة ، فسيعمل مع _any_ Futureويمكن حتى تضمينها مباشرة داخل Flutter.

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

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

لا أعتقد أننا نتفق على مدى تعقيد الميزة وإمكانية إعادة استخدامها.
هل لديك مثال يوضح أن هذه الميزة سهلة؟

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

لكنك أحد رواد التكنولوجيا في Flutter.
حتى عندما تواجه مشكلة ، سيكون لديك ما يكفي من المهارة للتوصل إلى حل على الفور.

ومع ذلك ، على الجانب الآخر ، لا يفهم عدد كبير من الناس ما هو الخطأ في الكود التالي:

FutureBuilder<User>(
  future: fetchUser(),
  builder: ...,
)

تم إثبات هذه الحقيقة من خلال مدى شعبية Q / AI المصنوع على StackOverflow .

لا تكمن المشكلة في أنه من المستحيل تجريد منطق الحالة بطريقة قابلة لإعادة الاستخدام وقوية (وإلا فلا فائدة من طرح هذه المشكلة).
المشكلة هي أنه يتطلب الوقت والخبرة للقيام بذلك.

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

تكمن المشكلة في أنها تعتمد حقًا على ماهية تطبيقك ، وشكل حالتك ، وما إلى ذلك. إذا كان السؤال هنا هو "كيف تدير حالة التطبيق" ، فإن الإجابة ليست مثل الخطافات ، فهناك الكثير من الوثائق التي تتحدث عن طرق مختلفة للقيام بذلك والتوصية بأساليب مختلفة لمواقف مختلفة ... بشكل أساسي ، هذه المجموعة من المستندات: https://flutter.dev/docs/development/data-and-backend/state-mgmt

هناك حالة مؤقتة وحالة تطبيق ، ولكن يبدو أن هناك حالة استخدام أخرى أيضًا: الحالة التي تهتم فقط بنوع واحد من عناصر واجهة المستخدم ولكنك مع ذلك تريد المشاركة بين هذا النوع من الأدوات.

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

علاوة على ذلك ، قد ترغب في تجميع هذا المنطق لجعله أكثر قابلية للتكوين لمشاريعك المستقبلية ، ولكن أيضًا للآخرين. إذا نظرت إلى موقع useHooks ، فسترى العديد من الأجزاء المنطقية التي useAuth فقم بكتابته مرة واحدة ولا داعي للقلق بشأن ما إذا كنت قد فاتتك مكالمة initState أو dispose ، أو ما إذا كانت الوظيفة غير المتزامنة لها then و catch . تتم كتابة الوظيفة مرة واحدة فقط حتى تختفي مساحة الخطأ بشكل أساسي. لذلك ، هذا النوع من الحلول ليس فقط أكثر قابلية للتكوين لأجزاء متعددة من نفس التطبيق وبين تطبيقات متعددة ، ولكنه أيضًا أكثر أمانًا للمبرمج النهائي.

ليس لدي أي اعتراض على الأشخاص الذين يستخدمون الخطافات. بقدر ما أستطيع أن أقول ، لا شيء يمنع ذلك. (إذا كان هناك شيء _ هو _ يمنع ذلك ، فيرجى تقديم خطأ حول ذلك.)

لا يتعلق هذا الخطأ بالخطافات ، بل يتعلق بـ "إعادة استخدام منطق الحالة مطول / صعب للغاية" ، وما زلت أعاني لفهم سبب تطلب ذلك أي تغييرات في Flutter. هناك العديد من الأمثلة (بما في ذلك الخطافات) التي توضح كيف يمكن تجنب الإسهاب من خلال هيكلة التطبيق بطريقة أو بأخرى ، وهناك الكثير من الوثائق حول هذا الموضوع بالفعل.

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

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

ما زلت أكافح لفهم لماذا يتطلب ذلك أي تغييرات في Flutter.

إن القول بأن هذه المشكلة قد تم حلها لأن المجتمع أنشأ حزمة يشبه القول بأن Dart لا تحتاج إلى فئات بيانات + أنواع اتحاد لأنني صنعت Freezed .
قد يكون Freezed محبوبًا جدًا من قبل المجتمع كحل لكل من هاتين المشكلتين ، ولكن لا يزال بإمكاننا القيام بعمل أفضل.

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

هذه المشكلة تحتاج ذلك.
تذكر: أحد أهداف فريق React هو أن تكون الخطافات جزءًا من اللغة ، مثل نوع JSX.

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

هذه حجة جيدة ، كما أوافق ، على الرغم من أن الفلسفة العامة لـ Flutter لها نواة صغيرة. لهذا السبب ، قمنا بإضافة وظائف جديدة بشكل متزايد كحزم حتى عندما تأتي من Google ، وشخصيات cf والرسوم المتحركة . يمنحنا ذلك قدرًا أكبر من المرونة للتعلم والتغيير بمرور الوقت. سنفعل الشيء نفسه في هذه المساحة ، ما لم يكن هناك سبب تقني مقنع لعدم كفاية الحزمة (ومع طرق الامتداد ، يكون هذا أقل احتمالًا من أي وقت مضى).

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

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

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

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

أنا أفهم هذا القلق ، لكن هذا لا يتعلق بحل إدارة الدولة.

هذه الميزة أقرب إلى Inheritedwidget & StatefulWidget. إنه مستوى بدائي منخفض ، يمكن أن يكون منخفضًا مثل ميزة اللغة.

قد تكون الخطافات مستقلة عن الإطار ، لكن هذا فقط من خلال الحظ.
كما ذكرت من قبل ، قد يكون هناك طريق آخر لهذه المشكلة:

context.onDispose(() {

});

والمستمعين حدث مماثل.
لكن من المستحيل تنفيذ ذلك خارج إطار العمل.

لا أعرف ما الذي سيأتي به الفريق.
لكن لا يمكننا استبعاد احتمال أن يكون هذا الحل بجوار العنصر مباشرةً

هل الإضافات تساعد في ذلك؟

(ربما ينبغي أن نتحدث عن ذلك في قضية مختلفة ، رغم ذلك. إنه نوع من خارج الموضوع هنا. أفضل حقًا إذا كانت لدينا مشكلة واحدة لكل مشكلة يراها الناس ، حتى نتمكن من مناقشة الحلول في المكان المناسب. إنها ليست وضح كيف سيساعد context.onDispose الإسهاب.)

أظن بشدة أن هناك بعض الاقتراحات اللغوية الجيدة حقًا التي يمكن أن نتوصل إليها فيما يتعلق بهذا الأمر.

أعتقد أنه سيكون من المفيد التحدث عنهم بشكل أكثر تحديدًا بدلاً من كيفية تمكينهم لمصطلح إدارة دولة معين. يمكننا بعد ذلك أن نفكر بجدية أكبر في ما يمكنهم تمكينه وما هي المقايضات التي قد تنطوي عليها.

على وجه الخصوص ، سنكون قادرين على التفكير في كيفية وما إذا كان بإمكانهم العمل في كل من أوقات تشغيل VM و JS

ليس من الواضح كيف يمكن أن يساعد context.onDispose الإسهاب.)

كما ذكرت من قبل ، فإن هذه المشكلة تتعلق بإمكانية إعادة استخدام الكود أكثر من الإسهاب. ولكن إذا تمكنا من إعادة استخدام المزيد من التعليمات البرمجية ، فيجب أن يؤدي ذلك ضمنيًا إلى تقليل الإسهاب.

الطريقة التي يرتبط بها context.onDispose بهذه المشكلة هي ، باستخدام الصيغة الحالية لدينا:

AnimationController controller;

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
}

<strong i="12">@override</strong>
void dispose() {
  controller.dispose();
}

المشكلة هي:

  • هذا مقترن بإحكام بتعريف الفئة ، لذلك لا يمكن إعادة استخدامه
  • مع نمو الأداة ، تصبح العلاقة بين التهيئة والتخلص أكثر صعوبة في القراءة نظرًا لوجود مئات الأسطر من التعليمات البرمجية في المنتصف.

باستخدام context.onDispose ، يمكننا القيام بما يلي:

<strong i="21">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

الجزء المثير للاهتمام هو:

  • لم يعد هذا مقترنًا بإحكام بتعريف الصنف ، لذلك يمكن استخراجه في دالة.
    يمكننا نظريًا أن يكون لدينا منطق شبه معقد:
    "" دارت
    AnimationController someReusableLogic (سياق BuildContext) {
    وحدة التحكم النهائية = AnimationController (...) ؛
    تحكم. onDispose (controller.dispose) ؛
    تحكم. إلى الأمام () ؛
    مستمع باطل () {}
    Controller.addListener (المستمع) ؛
    Context.onDispose (() => controller.removeListener (listener)) ؛
    }
    ...

@تجاوز
initState باطلة () {
وحدة تحكم = someReusableLogic (سياق) ؛
}
""

  • يتم تجميع كل المنطق معًا. حتى إذا كان طول القطعة 300 controller فلا يزال من السهل قراءة منطق

مشكلة هذا النهج هي:

  • context.myLifecycle(() {...}) غير قابل لإعادة التحميل
  • ليس من الواضح كيفية الحصول على خصائص قراءة someReusableLogic من StatefulWidget بدون ربط الوظيفة بتعريف الأداة.
    على سبيل المثال ، قد يتم تمرير AnimationController 's Duration كمعامل للأداة. لذلك نحن بحاجة للتعامل مع السيناريو حيث تتغير المدة.
  • من غير الواضح كيفية تنفيذ وظيفة تُرجع كائنًا يمكن أن يتغير بمرور الوقت ، دون الحاجة إلى اللجوء إلى ValueNotifier والتعامل مع المستمعين

    • هذا مهم بشكل خاص للحالات المحسوبة.


سأفكر في اقتراح لغة. لدي بعض الأفكار ، لكن لا شيء يستحق الحديث عنه الآن.

كما ذكرت من قبل ، فإن هذه المشكلة تتعلق بإمكانية إعادة استخدام الكود أكثر من الإسهاب

نعم. هل يمكنك تسجيل خطأ جديد ثم يتحدث عن ذلك على وجه التحديد؟ يسمى هذا الخطأ حرفياً "إعادة استخدام منطق الحالة مطول / صعب للغاية". إذا لم يكن الإسهاب هو المشكلة ، فهذه ليست هي المشكلة.

باستخدام context.onDispose ، يمكننا القيام بما يلي:

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

لست متأكدًا من سبب ارتباط context بهذا (و onDispose ينتهك اصطلاحات التسمية الخاصة بنا). إذا كنت تريد فقط طريقة لتسجيل الأشياء لتشغيلها أثناء التخلص منها ، فيمكنك القيام بذلك بسهولة اليوم:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  <strong i="17">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

أطلق عليه مثل هذا:

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  TextEditingController controller;

  <strong i="21">@override</strong>
  void initState() {
    super.initState();
    controller = TextEditingController(text: 'button');
    queueDispose(controller.dispose);
  }

  ...
AnimationController someReusableLogic(BuildContext context) {
  final controller = AnimationController(...);
  controller.onDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  context.onDispose(() => controller.removeListener(listener));
}
...

<strong i="25">@override</strong>
void initState() {
  controller = someReusableLogic(context);
}

يمكنك عمل ذلك أيضا:

AnimationController someReusableLogic<T extends StatefulWidget>(StateHelper<T> state) {
  final controller = AnimationController(...);
  state.queueDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  return controller;
}
...

<strong i="6">@override</strong>
void initState() {
  controller = someReusableLogic(this);
}

مشكلة هذا النهج هي:

  • context.myLifecycle(() {...}) غير قابل لإعادة التحميل أثناء التشغيل

في هذا السياق ، لا يبدو أنه مهم لأنه يتعلق فقط بالأشياء التي يتم استدعاؤها في initState؟ هل فاتني شيء؟

  • ليس من الواضح كيفية الحصول على خصائص قراءة someReusableLogic من StatefulWidget بدون ربط الوظيفة بتعريف عنصر واجهة المستخدم بإحكام.
    على سبيل المثال ، قد يتم تمرير AnimationController 's Duration كمعامل للأداة. لذلك نحن بحاجة للتعامل مع السيناريو حيث تتغير المدة.

من السهل جدًا إضافة قائمة انتظار didChangeWidget تمامًا مثل قائمة انتظار التخلص:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;
  List<VoidCallback> _didUpdateWidgetQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  void queueDidUpdateWidget(VoidCallback callback) {
    _didUpdateWidgetQueue ??= <VoidCallback>[];
    _didUpdateWidgetQueue.add(callback);
  }

  <strong i="24">@override</strong>
  void didUpdateWidget(T oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_didUpdateWidgetQueue != null) {
      for (VoidCallback callback in _didUpdateWidgetQueue)
        callback();
    }
  }

  <strong i="25">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

AnimationController conditionalAnimator(StateHelper state, ValueGetter<bool> isAnimating, VoidCallback listener) {
  final controller = AnimationController(vsync: state as TickerProvider, duration: const Duration(seconds: 1));
  state.queueDispose(controller.dispose);
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  if (isAnimating())
    controller.repeat();
  state.queueDidUpdateWidget(() {
    if (isAnimating()) {
      controller.repeat();
    } else {
      controller.stop();
    }
  });
  return controller;
}

تستخدم مثل هذا:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(animating: false),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.animating}) : super(key: key);

  final bool animating;

  <strong i="7">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage>, SingleTickerProviderStateMixin {
  AnimationController controller;

  <strong i="8">@override</strong>
  void initState() {
    super.initState();
    controller = conditionalAnimator(this, () => widget.animating, () { print(controller.value); });
  }

  <strong i="9">@override</strong>
  Widget build(BuildContext context) {
    return Center(
      child: FadeTransition(
        opacity: controller,
        child: Text('Hello', style: TextStyle(fontSize: 100.0, color: Colors.white)),
      ),
    );
  }
}
  • من غير الواضح كيفية تنفيذ وظيفة تُرجع كائنًا يمكن أن يتغير بمرور الوقت ، دون الحاجة إلى اللجوء إلى ValueNotifier والتعامل مع المستمعين

    • هذا مهم بشكل خاص للحالات المحسوبة.

لست متأكدًا مما يعنيه هذا هنا ، ما الخطأ في ValueNotifier ، ولنقل ، ValueListenableBuilder؟

كما ذكرت من قبل ، فإن هذه المشكلة تتعلق بإمكانية إعادة استخدام الكود أكثر من الإسهاب

نعم. هل يمكنك تسجيل خطأ جديد ثم يتحدث عن ذلك على وجه التحديد؟ يسمى هذا الخطأ حرفياً "إعادة استخدام منطق الحالة مطول / صعب للغاية". إذا لم يكن الإسهاب هو المشكلة ، فهذه ليست هي المشكلة.

بدأت أشعر بعدم الارتياح تجاه هذه المناقشة. لقد أجبت بالفعل على هذه النقطة من قبل:
موضوع هذه المسألة هو إعادة الاستخدام ، وتمت مناقشة الإسهاب كنتيجة لمشكلة إعادة الاستخدام ؛ ليس كموضوع أساسي.

لا يوجد سوى نقطة واحدة في التعليق العلوي تشير إلى الإسهاب ، وذلك مع StreamBuilder ، والذي يستهدف بشكل أساسي مستويين من المسافات البادئة.

لست متأكدًا من سبب أهمية السياق في هذا [...]. إذا كنت تريد فقط طريقة لتسجيل الأشياء لتشغيلها أثناء التخلص منها ، فيمكنك القيام بذلك بسهولة اليوم:

عندما طرحت context.onDispose ، ذكرت صراحة أنني لا أعتقد أنه حل جيد.
شرحت ذلك لأنك سألت عن علاقته بالمناقشة.

بالنسبة لسبب context بدلاً من StateHelper ، فذلك لأن هذا أكثر مرونة (مثل العمل مع StatelessWidget)

Context.myLifecycle (() {...}) ليست قابلة لإعادة التحميل السريع

في هذا السياق ، لا يبدو أنه مهم لأنه يتعلق فقط بالأشياء التي يتم استدعاؤها في initState؟ هل فاتني شيء؟

قد نغير:

initState() {
  context.myLifecycle(() => print('hello'));
}

إلى:

initState() {
  context.myLifecycle(() => print('world'));
}

لن يتم تطبيق التغييرات على رد الاتصال myLifecycle .

لكن إذا استخدمنا:

myLifecycle() {
  super.myLifecycle();
  print('hello');
}

ثم سوف تعمل إعادة التحميل الساخنة.

لست متأكدًا مما يعنيه هذا هنا ، ما الخطأ في ValueNotifier ، ولنقل ، ValueListenableBuilder؟

تم تصميم بناء الجملة هذا لتجنب الاضطرار إلى استخدام البناة ، لذلك عدنا إلى المشكلة الأصلية.

علاوة على ذلك ، إذا كنا نريد حقًا جعل وظيفتنا قابلة للتكوين ، فبدلاً من اقتراح ValueGetter + queueDidUpdateWidget ، يجب أن تأخذ الوظائف ValueNotifier كمعامل:

AnimationController conditionalAnimator(StateHelper state, ValueListenable<bool> isAnimating, VoidCallback listener) {
...
}

حيث قد نرغب في الحصول على isAnimating من مكان آخر غير didUpdateWidget اعتمادًا على الأداة التي تستخدم هذه الوظيفة.
في مكان واحد ، قد يكون didUpdateWidget ؛ في مكان آخر قد يكون قد تم تغيير التبعيات ؛ وفي مكان آخر ، قد يكون داخل رد اتصال stream.listen .

ولكن بعد ذلك نحتاج إلى طريقة لتحويل هذه السيناريوهات إلى ValueNotifier بسهولة وجعل وظيفتنا تستمع إلى هذا المخطر.
لذلك نحن نجعل حياتنا أكثر صعوبة.
إنه أكثر واقعية وأسهل استخدام ConditionalAnimatorBuilder من هذا النمط على ما أعتقد.

بالنسبة لسبب context بدلاً من StateHelper ، فذلك لأن هذا أكثر مرونة (مثل العمل مع StatelessWidget)

أداة StatelessWidget مخصصة للأدوات عديمة الحالة. بيت القصيد هو أنهم لن ينشئوا حالة ، أو يتخلصوا من الأشياء ، أو يتفاعلوا مع didUpdateWidget ، إلخ.

إعادة الشيء الساخن ، نعم. لهذا السبب نستخدم الطرق بدلاً من وضع الإغلاق في initState.

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

  • التصريح بكيفية التخلص من شيء ما يجب أن يتم في نفس المكان المخصص له ...
  • ... والمكان الذي يخصصه يحتاج للتشغيل مرة واحدة فقط لأنه يخصصه ...
  • ... ويحتاج إلى العمل مع إعادة التحميل السريع (والذي بحكم التعريف لا يعيد تشغيل الكود الذي يعمل مرة واحدة فقط) ...
  • ... ويجب أن يكون قادرًا على إنشاء حالة تعمل مع عناصر واجهة مستخدم عديمة الحالة (والتي ليس لها حالة حسب التعريف) ...
  • ... ويحتاج إلى تمكين الارتباط بأشياء مثل didUpdateWidget و didChangeDependencies ...

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

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

يبدو أن هناك العديد من الرغبات الحصرية هنا ، منتشرة عبر العديد من التعليقات في هذا الخطأ:

أنهم لا يستبعدون بعضهم البعض. تقوم الخطافات بكل واحد من هؤلاء. لن أخوض في التفاصيل لأننا لا نريد التركيز على الحلول ، لكنهم يقومون بتحديد جميع المربعات.

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

ما زلت أفشل في فهم كيف فشل هذا التعليق الرئيسي في القيام بذلك.
ليس من الواضح بالنسبة لي ما هو غير واضح للآخرين.

في الواقع ، الكثير مما تصفه مشابه جدًا لكيفية تصميم Jetpack Compose. لست من أشد المعجبين بهذا التصميم لأنه يتطلب سحر المترجم ، لذا فإن تصحيح أخطاء ما يحدث أمر صعب حقًا ، ولكن ربما يكون الأمر أكثر ملاءمة لك؟

لست معتادًا على ذلك ، ولكن مع البحث السريع ، أود أن أقول _نعم_.

أنهم لا يستبعدون بعضهم البعض.

هل جميع النقاط التي ذكرتها أعلاه جزء من المشكلة التي نحاول حلها هنا؟

لكنهم حددوا كل المربعات

هل يمكنك سرد الصناديق؟

ما زلت أفشل في فهم كيف فشل هذا التعليق الرئيسي في القيام بذلك.

على سبيل المثال ، يقول OP صراحةً أن المشكلة تتعلق بـ StatefulWidgets ، لكن أحد التعليقات الأخيرة حول هذه المسألة قال إن اقتراحًا معينًا لم يكن جيدًا لأنه لم يعمل مع StatelessWidgets.

في OP تقول:

من الصعب إعادة استخدام منطق State . إما أن ينتهي بنا الأمر مع طريقة build معقدة ومتداخلة بعمق أو يتعين علينا نسخ ولصق المنطق عبر أدوات متعددة.

لذلك من هذا المنطلق أفترض أن المتطلبات تشمل:

  • يجب ألا يكون الحل متداخلًا بعمق.
  • يجب ألا يتطلب الحل الكثير من التعليمات البرمجية المماثلة في الأماكن التي تحاول إضافة حالة.

النقطة الأولى (حول التعشيش) تبدو جيدة. بالتأكيد لا تحاول الإيحاء بضرورة القيام بأشياء متداخلة بعمق. (ومع ذلك ، قد نختلف بشأن ما هو متداخل بعمق ؛ لم يتم تعريفه هنا. تشير التعليقات الأخرى لاحقًا إلى أن المنشئين يتسببون في رمز متداخل بعمق ، ولكن من واقع خبرتي ، فإن البناة جيدون جدًا ، كما هو موضح في الكود الذي ذكرته سابقًا.)

يبدو أن النقطة الثانية مباشرة هي شرط ألا يكون لدينا الإسهاب. لكنك أوضحت بعد ذلك عدة مرات أن الأمر لا يتعلق بالإسهاب.

البيان التالي الذي يجعله البروتوكول الاختياري يصف المشكلة هو:

إعادة استخدام منطق State عبر عدة StatefulWidget أمر صعب للغاية ، بمجرد أن يعتمد هذا المنطق على دورات حياة متعددة.

بصراحة ، أنا لا أعرف حقًا ما يعنيه هذا. عادةً ما تعني كلمة "صعب" بالنسبة لي أن شيئًا ما ينطوي على الكثير من المنطق المعقد الذي يصعب فهمه ، ولكن تخصيص أحداث دورة الحياة والتخلص منها والرد عليها أمر بسيط للغاية. العبارة التالية التي تعطي مشكلة (هنا أتخطى المثال الموصوف صراحة بأنه "غير معقد" وبالتالي ليس وصفًا للمشكلة) هو:

تبدأ المشكلة عندما نريد توسيع نطاق هذا النهج.

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

ويدعم ذلك أيضًا العبارة التالية التي تصف المشكلة:

نسخ ولصق هذا المنطق في كل مكان "يعمل" ، ولكنه يخلق نقطة ضعف في الكود الخاص بنا:

  • قد يكون من السهل نسيان إعادة كتابة إحدى الخطوات (مثل نسيان الاتصال بـ dispose )

من المفترض أن يكون الأمر صعبًا للغاية لأن الإسهاب يجعل من السهل ارتكاب خطأ عند نسخ الشفرة ولصقها؟ لكن مرة أخرى ، عندما حاولت معالجة هذه المشكلة ، التي يمكن أن أصفها بـ "الإسهاب" ، قلت إن المشكلة ليست الإسهاب.

  • يضيف الكثير من الضوضاء في الكود

مرة أخرى ، يبدو هذا وكأنه مجرد قول الإسهاب / المعياري لي ، لكنك أوضحت مرة أخرى أن الأمر ليس كذلك.

ما تبقى من OP هو مجرد وصف الحلول التي لا تحبها ، لذلك من المفترض أنها لا تصف المشكلة.

هل هذا يفسر كيف فشل البروتوكول الاختياري في شرح المشكلة؟ يبدو أن كل شيء في OP الذي يصف مشكلة في الواقع يصف الإسهاب ، ولكن في كل مرة أقترح أن هذه هي المشكلة ، فأنت تقول إنها ليست كذلك وأن هناك مشكلة أخرى.

أعتقد أن سوء الفهم يتلخص في معنى كلمة.
على سبيل المثال:

يضيف الكثير من الضوضاء في الكود

مرة أخرى ، يبدو هذا وكأنه مجرد قول الإسهاب / المعياري لي ، لكنك أوضحت مرة أخرى أن الأمر ليس كذلك.

لا تتعلق هذه النقطة بعدد controller.dispose() ، ولكن القيمة التي تجلبها سطور التعليمات البرمجية هذه للقارئ.
يجب أن يكون هذا الخط موجودًا دائمًا ويكون دائمًا هو نفسه. على هذا النحو ، فإن قيمتها بالنسبة للقارئ تكاد تكون معدومة.

ما يهم ليس وجود هذا الخط ، بل غيابه.

تكمن المشكلة في أنه كلما زادت قيمة هذا controller.dispose() لدينا ، زاد احتمال تفويتنا لمشكلة فعلية في طريقة التخلص الخاصة بنا.
إذا كان لدينا وحدة تحكم واحدة و 0 نتخلص منها ، فمن السهل الإمساك بها
إذا كان لدينا 100 وحدة تحكم و 99 تخلص منها ، فسيكون العثور على القطعة المفقودة أمرًا صعبًا.

إذن لدينا:

من المفترض أن يكون الأمر صعبًا للغاية لأن الإسهاب يجعل من السهل ارتكاب خطأ عند نسخ الشفرة ولصقها؟ لكن مرة أخرى ، عندما حاولت معالجة هذه المشكلة ، التي يمكن أن أصفها بـ "الإسهاب" ، قلت إن المشكلة ليست الإسهاب.

كما ذكرت في النقطة السابقة ، ليست كل سطور الرموز متساوية.

إذا قارنا:

+ T state;

<strong i="24">@override</strong>
void initState() {
  super.initState();
+  state = widget.valueNotifier.value;
+  widget.valueNotifier.addListener(_listener);
}

+ void _listener() => seState(() => state = widget.valueNotifier.value);

void dispose() {
+ widget.valueNotifier.removeListener(_listener);
  super.dispose();
}

ضد:

+ ValueListenableBuilder<T>(
+   valueListenable: widget.valueNotifier,  
+   builder: (context, value, child) {

+    },
+ );

ثم كلا المقتطفين لهما نفس عدد الأسطر ويفعلان نفس الشيء.
لكن يفضل ValueListenableBuilder .

والسبب في ذلك ، ليس عدد السطور هو المهم ، ولكن ما هي هذه الخطوط.

يحتوي المقتطف الأول على:

  • 1 إعلان ملكية
  • 1 طريقة التصريح
  • 1 مهمة
  • 2 طريقة المكالمات
  • وكلها موزعة على دورتين مختلفتين من الحياة. 3 إذا قمنا بتضمين البناء

يحتوي المقتطف الثاني على:

  • 1 فئة مثيل
  • 1 وظيفة مجهولة
  • لا توجد دورة حياة. 1 إذا قمنا بتضمين البناء

مما يجعل ValueListenableBuilder _simpler_.

وهناك أيضًا ما لا تقوله هذه السطور:
يتعامل ValueListenableBuilder مع تغيير valueListenable بمرور الوقت.
حتى في السيناريو حيث لا يتغير widget.valueNotifier بمرور الوقت كما نتحدث ، فهذا لا يضر.
في يوم من الأيام ، قد يتغير هذا البيان. في هذه الحالة ، يتعامل ValueListenableBuilder مع السلوك الجديد برشاقة ، بينما مع المقتطف الأول ، لدينا الآن خطأ.

لذلك ، فإن ValueListenableBuilder ليست أبسط فحسب ، بل إنها أيضًا أكثر مرونة للتغيرات في الكود - لنفس عدد الأسطر بالضبط.


مع ذلك ، أعتقد أنه يمكننا الاتفاق على أن ValueListenableBuilder هي الأفضل.
السؤال إذن هو ، "لماذا لا يكون لديك ما يعادل ValueListenableBuilder لكل منطق حالة يمكن إعادة استخدامه؟"

على سبيل المثال ، بدلاً من:

final controller = TextEditingController(text: 'hello world');
...
controller.dispose();

سيكون لدينا:

TextEditingControllerBuilder(
  initialText: 'hello world',
  builder: (context, controller) {

  },
);

مع الميزة الإضافية التي يمكن إعادة تحميلها إلى initialText .

قد يكون هذا المثال تافهًا بعض الشيء ، لكن يمكننا استخدام هذا المبدأ لمنطق أكثر تقدمًا لحالة قابلة لإعادة الاستخدام (مثل ModeratorBuilder الخاص بك).

هذا "جيد" في قصاصات صغيرة. لكنها تسبب بعض المشاكل لأننا نريد توسيع نطاق النهج:

  • يعود البناة إلى موضوع "الضجيج الشديد".

على سبيل المثال ، رأيت بعض الأشخاص يديرون نموذجهم بهذه الطريقة:

class User {
  final ValueNotifier<String> name;
  final ValueNotifier<int> age;
  final ValueNotifier<Gender> gender;
}

ولكن بعد ذلك ، قد ترغب الأداة في الاستماع إلى كل من name و age و gender دفعة واحدة.
مما يعني أنه يتعين علينا القيام بما يلي:

return ValueListenableBuilder<String>(
  valueListenable: user.name,
  builder: (context, userName, _) {
    return ValueListenableBuilder<int>(
      valueListenable: user.age,
      builder: (context, userAge, _) {
        return ValueListenableBuilder<Gender>(
          valueListenable: user.gender,
          builder: (context, userGender, _) {
            return Text('$userGender. $userName ($userAge)');
          },
        );
      },
    );
  },
);

من الواضح أن هذا ليس مثاليًا. أزلنا التلوث داخل initState / dispose لتلويث طريقة build .

(دعنا نتجاهل Listenable.merge من أجل المثال. لا يهم هنا ؛ الأمر يتعلق أكثر بالتأليف)

إذا استخدمنا Builders على نطاق واسع ، فمن السهل أن نرى أنفسنا في هذا السيناريو الدقيق - وبدون ما يعادل Listenable.merge (لا يعجبني هذا المنشئ ، لتبدأ بـ 😛)

  • كتابة منشئ مخصص مملة

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

    من المرجح أن يخبز الأشخاص حلاً مخصصًا لإدارة الدولة ويحتمل أن ينتهي بهم الأمر بتعليمات برمجية سيئة.

  • التلاعب بشجرة البناة أمر شاق

    لنفترض أننا أردنا إزالة ValueListenableBuilder في مثالنا السابق أو إضافة واحد جديد ، فهذا ليس بالأمر السهل.
    يمكننا قضاء بضع دقائق عالق في العد () و {} لفهم سبب عدم ترجمة الكود الخاص بنا.


الخطافات موجودة لحل مشكلات Builder التي ذكرناها للتو.

إذا قمنا بإعادة تشكيل المثال السابق إلى الخطافات ، فسنحصل على:

final userName = useValueListenable(user.name);
final useAge = useValueListenable(user.age);
final useGender = useValueListenable(user.gender);

return Text('$userGender. $userName ($userAge)');

إنه مطابق للسلوك السابق ، لكن الكود يحتوي الآن على مسافة بادئة خطية.
مما يعني:

  • الشفرة بشكل جذري أكثر قابلية للقراءة
  • من الأسهل تحريرها. لا داعي للخوف () {}؛ لإضافة سطر جديد.

هذا واحد من provider يحب. لقد أزال الكثير من التداخل من خلال تقديم MultiProvider .

وبالمثل ، على عكس نهج initState / dispose ، فإننا نستفيد من إعادة التحميل السريع.
إذا أضفنا useValueListenable جديدًا ، فسيتم تطبيق التغيير على الفور.

وبالطبع ، لا يزال لدينا القدرة على استخراج العناصر الأولية القابلة لإعادة الاستخدام:

String useUserLabel(User user) {
  final userName = useValueListenable(user.name);
  final useAge = useValueListenable(user.age);
  final useGender = useValueListenable(user.gender);

  return '$userGender. $userName ($userAge)';
}

Widget build(context) {
  final label = useUserLabel(user);
  return Text(label);
}

ويمكن أتمتة هذا التغيير باستخدام extract as function ، والذي سيعمل في معظم السيناريوهات.


هل هذا الجواب على سؤالك؟

بالتأكيد. لكن المشكلة في شيء كهذا هي أنه لا يحتوي على معلومات كافية لفعل الشيء الصحيح. على سبيل المثال:

Widget build(context) {
  if (random.nextBool())
    final title = useLabel(title);
  final label = useLabel(name);
  return Text(label);
}

... سينتهي به الأمر إلى أن تكون عربات التي تجرها الدواب بطرق مربكة حقًا.

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

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

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

const [title, setTitle] = useLabel("title");

يمكن أن يكون مكافئ Dart مشابهًا ، فهو أطول فقط بسبب عدم وجود تفريغ مثل JS:

var titleHook = useLabel("title");
String title = titleHook.property;
Function setTitle = titleHook.setter;

إذا كنت تريد منطقًا شرطيًا ، فيمكنك حينئذٍ أن تقرر استخدام title في طريقة الإنشاء _ بعد استدعائها في المستوى الأعلى_ ، لأنه الآن لا يزال ترتيب الاستدعاء محفوظًا. تم شرح العديد من هذه المشكلات التي أثارتها في مستند الخطافات الذي ربطته أعلاه.

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

سلوك التصحيح / الشرطي ليس مشكلة. هذا هو سبب أهمية المكون الإضافي للمحلل. مثل هذا البرنامج المساعد من شأنه:

  • تحذير إذا كانت الدالة تستخدم خطافًا دون تسميتها useMyFunction
  • تحذير إذا تم استخدام الخطاف بشكل مشروط
  • تحذير إذا تم استخدام خطاف في حلقة / رد.

هذا يغطي جميع الأخطاء المحتملة. أثبت React أن هذا أمر ممكن.

ثم يتبقى لنا الفوائد:

  • كود أكثر قابلية للقراءة (كما هو موضح سابقًا)
  • أفضل الساخنة إعادة التحميل
  • كود أكثر قابلية لإعادة الاستخدام / للتكوين
  • أكثر مرونة - يمكننا بسهولة إنشاء حالات محسوبة.

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

على سبيل المثال ، يمكن أن يكون لدينا:

class Example extends HookWidget {
  final int userId;

  Widget build(context) {
    // Calls fetchUser whenever userId changes
    // It is the equivalent to both initState and didUpdateWidget
    final future = useMemo1(() => fetchUser(userId), userId);

    final snapshot = useFuture(future);
    if (!snapshot.hasData)
      return Text('loading');
    return Text(snapshot.data.name);
  }  
}

يتيح خطاف useMemo تحسينات سهلة للأداء والتعامل مع كل من init + التحديث بشكل إعلاني ، مما يتجنب الأخطاء أيضًا.

هذا شيء يفوته اقتراح Property / context.onDispose .
يصعب استخدامها للحالات التصريحية دون اقتران المنطق بإحكام بدورة الحياة أو تعقيد الكود بـ ValueNotifier .

المزيد عن سبب عدم عرض اقتراح ValueGetter عمليًا:

قد نرغب في إعادة بناء:

final int userId;

Widget build(context) {
  final future = useMemo1(() => fetchUser(userId), userId);

إلى:

Widget build(context) {
  final userId = Model.of(context).userId;
  final future = useMemo1(() => fetchUser(userId), userId);

مع الخطافات ، يعمل هذا التغيير بلا عيب ، حيث إن useMemo غير مرتبط بأي دورة حياة.

ولكن مع Property + ValueGetter ، سيتعين علينا تغيير تنفيذ Property لإنجاز هذا العمل - وهو أمر غير مرغوب فيه حيث قد يكون رمز Property أعيد استخدامها في أماكن متعددة. لذلك فقدنا قابلية إعادة الاستخدام مرة أخرى.

FWIW هذا المقتطف يعادل:

class Example extends StatefulWidget {
  final int userId;
  <strong i="45">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<User> future;

  <strong i="46">@override</strong>
  void initState() {
    super.initState();
    future = fetchUser(widget.userId);
  }

  <strong i="47">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.userId != widget.userId) {
      future = fetchUser(widget.userId);
    }
  }

  <strong i="48">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future,
      builder: (context, snapshot) {
        if (!snapshot.hasData)
          return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

أفترض أنه سيتعين علينا إيجاد حل يحل نفس المشكلات التي يذكرها rousselGit ولكن أيضًا لديه قابلية القراءة وتصحيح الأخطاء في الاعتبار ، إذن. تمتلك Vue تطبيقًا خاصًا به قد يكون أكثر انسجامًا مع ما تبحث عنه ، حيث لا تسبب الشرطية أو طلب الاتصال أخطاءً كما هو الحال في React.

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

على أي حال ، لا أعتقد أننا سنتوصل أبدًا إلى إجماع. يبدو أننا نختلف حتى على ما هو مقروء

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

  • نظام البرنامج المساعد محلل مناسب
  • القدرة على استخدام الحزم داخل dartpad

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

إليك نسخة جديدة من فكرة Property . إنه يتعامل مع didUpdateWidget والتخلص (ويمكن بسهولة إجراءه للتعامل مع أشياء أخرى مثل didChangeDependencies) ؛ يدعم إعادة التحميل السريع (يمكنك تغيير الكود الذي يسجل الخاصية وإعادة التحميل السريع ، وسوف يفعل الشيء الصحيح) ؛ إنه آمن من النوع دون الحاجة إلى أنواع صريحة (يعتمد على الاستدلال) ؛ يحتوي على كل شيء في مكان واحد باستثناء إعلان الملكية والاستخدام ، ويجب أن يكون الأداء جيدًا بشكل معقول (على الرغم من أنه ليس جيدًا مثل الطرق الأكثر تفصيلاً للقيام بالأشياء).

الملكية / مدير الممتلكات:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

typedef InitStateCallback<T> = T Function(T oldValue);
typedef DidUpdateWidgetCallback<T, W extends StatefulWidget> = T Function(W oldWidget);

class Property<T, W extends StatefulWidget> {
  Property({
    T value,
    this.initState,
    this.didUpdateWidget,
    this.dispose,
  }) : _value = value;

  T get value {
    assert(_value != null);
    return _value;
  }
  T _value;

  final InitStateCallback<T> initState;
  void _initState(Property<T, W> oldProperty) {
    if (initState != null)
      _value = initState(oldProperty?.value);
    assert(_value != null);
  }

  final DidUpdateWidgetCallback<T, W> didUpdateWidget;
  void _didUpdateWidget(StatefulWidget oldWidget) {
    if (didUpdateWidget != null) {
      final T newValue = didUpdateWidget(oldWidget);
      if (newValue != null)
        _value = newValue;
    }
  }

  final ValueSetter<T> dispose;
  void _dispose() {
    if (dispose != null)
      dispose(value);
  }
}

mixin PropertyManager<W extends StatefulWidget> on State<W> {
  final Set<Property<Object, W>> _properties = <Property<Object, W>>{};
  bool _ready = false;

  Property<T, W> register<T>(Property<T, W> oldProperty, Property<T, W> property) {
    assert(_ready);
    if (oldProperty != null) {
      assert(_properties.contains(oldProperty));
      _properties.remove(oldProperty);
    }
    assert(property._value == null);
    property._initState(oldProperty);
    _properties.add(property);
    return property;
  }

  <strong i="9">@override</strong>
  void initState() {
    super.initState();
    _ready = true;
    initProperties();
  }

  <strong i="10">@override</strong>
  void reassemble() {
    super.reassemble();
    initProperties();
  }

  <strong i="11">@protected</strong>
  <strong i="12">@mustCallSuper</strong>
  void initProperties() { }

  <strong i="13">@override</strong>
  void didUpdateWidget(W oldWidget) {
    super.didUpdateWidget(oldWidget);
    for (Property<Object, W> property in _properties)
      property._didUpdateWidget(oldWidget);
  }

  <strong i="14">@override</strong>
  void dispose() {
    _ready = false;
    for (Property<Object, W> property in _properties)
      property._dispose();
    super.dispose();
  }
}

إليك كيف ستستخدمه:

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'properties.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Example(userId: 1),
    );
  }
}

class User {
  User(this.name);
  final String name;
}

Future<User> fetchUser(int userId) async {
  await Future.delayed(const Duration(seconds: 2));
  return User('user$userId');
}

class Example extends StatefulWidget {
  Example({ Key key, this.userId }): super(key: key);

  final int userId;

  <strong i="19">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  Property future;

  <strong i="20">@override</strong>
  void initProperties() {
    super.initProperties();
    future = register(future, Property(
      initState: (_) => fetchUser(widget.userId),
      didUpdateWidget: (oldWidget) {
        if (oldWidget.userId != widget.userId)
          return fetchUser(widget.userId);
      }
    ));
  }

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

للراحة ، يمكنك إنشاء فئات فرعية معدة للخاصية لأشياء مثل AnimationControllers وما إلى ذلك ،

يمكنك على الأرجح إنشاء نسخة من هذا من شأنها أن تعمل في أساليب State.build أيضًا ...

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

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

إنه أمر متعامد من المحادثة حول أي تغيير مقترح بحد ذاته ، لكنني أعتقد أن ما سمعته من escamoteur و rousselGit وآخرين هنا وفي أي مكان آخر هو أن كونك _in_ يُنظر إليه على أنه طريقة مهمة لإثبات شرعية عنصر معين مقاربة. صححني إذا كنت لا توافق.

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

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

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

تم توثيق سياسة الحزمة الخاصة بنا هنا: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#deciding -where-to-put-code

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

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

مع Provider ، يشحن Flutter بدائية مدمجة لحل هذا النوع من المشاكل: InheritedWidgets.
يضيف المزود طبقة معتدلة في الجزء العلوي فقط لجعلها "أجمل".

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

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

أعتقد أن اقتراح الملكية فشل في حل هدف رئيسي لهذه المشكلة: يجب ألا يهتم منطق الدولة بمصدر المعايير وفي أي حالة يتم تحديثها.

يزيد هذا الاقتراح من قابلية القراءة إلى حد ما عن طريق إعادة تجميع كل المنطق في مكان واحد ؛ لكنه فشل في حل مشكلة إعادة الاستخدام

وبشكل أكثر تحديدًا ، لا يمكننا استخراج:

Property(
  initState: (_) => fetchUser(widget.userId),
  didUpdateWidget: (oldWidget) {
    if (oldWidget.userId != widget.userId)
      return fetchUser(widget.userId);
  }
)

من _ExampleState وأعد استخدامه في أداة مختلفة ، حيث أن المنطق مرتبط بـ Example و initState + didUpdateWidget

كيف ستبدو مع الخطافات؟

أتفق مع timsneath بعد أن رأيت شيئًا مشابهًا في مجتمع Rust. من الصعب جدًا استخراج شيء ما من النواة بمجرد إخراجه. تم تحديد نمط BLoC قبل ظهور المزود ولكن الموفر الآن هو الإصدار الموصى به. ربما يمكن أن تكون flutter_hooks هي النسخة "المباركة" بنفس الطريقة. أقول هذا لأنه قد يكون هناك تحسينات في المستقبل على الخطافات التي يبتكرها الناس. لا يمكن لـ React ، بعد أن كان لديها خطافات الآن ، تغييرها أو الخروج منها. يجب أن يدعموها ، مثلما يفعلون مع مكونات الطبقة ، بشكل أساسي إلى الأبد ، لأنهم في الجوهر. لذلك ، أنا أتفق مع فلسفة الحزمة.

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

كيف ستبدو مع الخطافات؟

بدون استخدام أي خطافات بدائية يتم شحنها بواسطة React / flutter_hooks ، يمكن أن يكون لدينا:

class FetchUser extends Hook<AsyncSnapshot<User>> {
  const FetchUser(this.userId);
  final int userId;

  <strong i="8">@override</strong>
  _FetchUserState createState() => _FetchUserState();
}

class _FetchUserState extends HookState<AsyncSnapshot<User>, FetchUser> {
  Future<User> userFuture;

  <strong i="9">@override</strong>
  void initHook() {
    userFuture = fetchUser(hook.userId);
  }  

  void didUpdateHook(FetchUser oldHook) {
    if (oldHook.userId != hook.userId)
      userFuture = fetchUser(hook.userId);
  }


  <strong i="10">@override</strong>
  User build() {
    return useFuture(userFuture);
  }
}

ثم استخدم:

class Example extends HookWidget {
  const Example({Key key, this.userId}) : super(key: key);

  final int userId;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    AsyncSnapshot<User> user = use(FetchUser(userId));

    if (!user.hasData)
      return CircularProgressIndicator();
    return Text(user.data.name);
  }
}

في هذه الحالة ، يكون المنطق مستقلاً تمامًا عن Example ودورات حياة StatefulWidget .
لذلك يمكننا إعادة استخدامه في عنصر واجهة مستخدم مختلف يدير userId مختلف. ربما تكون هذه الأداة الأخرى عبارة عن StatefulWidget تدير userId داخليًا. ربما سيحصل على userId من أداة InheritedWidget بدلاً من ذلك.

يجب أن يوضح هذا التركيب أن الخطافات تشبه كائنات State مستقلة بدورات حياتها الخاصة.

كملاحظة جانبية ، أحد عيوب نهج الحزمة أولاً هو: من غير المرجح أن ينشر مؤلفو الحزم الحزم التي تعتمد على الخطافات لحل المشكلات.

على سبيل المثال ، إحدى المشكلات الشائعة التي واجهها مستخدمو الموفر هي أنهم يريدون التخلص تلقائيًا من حالة الموفر عند عدم استخدامه.
تكمن المشكلة في أن مستخدمي المزود مغرمون تمامًا بصيغة context.watch / context.select ، على عكس الصيغة المطولة Consumer(builder: ...) / Selector(builder:...) .
لكن لا يمكننا الحصول على كلٍّ من هذه الصيغة اللطيفة _و_ حل المشكلة المذكورة سابقًا بدون خطافات (أو https://github.com/flutter/flutter/pull/33213 ، التي تم رفضها).

المشكلة هي:
لا يمكن للمزود الاعتماد على flutter_hooks لحل هذه المشكلة.
نظرًا لمدى شهرة المزود ، سيكون من غير المعقول الاعتماد على الخطافات.

لذلك في النهاية ، اخترت:

  • forking Provider (تحت الاسم الرمزي Riverpod )
  • نتيجة لذلك ، تفقد "مفضلات Flutter" / توصيات Google طوعًا
  • حل هذه المشكلة (وبعض أكثر)
  • أضف اعتمادًا على الخطافات لتقديم بناء جملة يرغب فيه الأشخاص الذين يستمتعون بـ context.watch .

أنا راضٍ تمامًا عما توصلت إليه ، حيث أعتقد أنه يجلب تحسنًا كبيرًا على المزود (يجعل InheritedWidgets ترجمة آمنة).
لكن الطريق إلى هناك ترك لي مذاقًا سيئًا.

هناك ثلاثة اختلافات أساسية بقدر ما أستطيع التمييز بين إصدار الخطافات وإصدار الخاصية:

  • إصدار الخطافات هو رمز دعم أكثر بكثير
  • إصدار الخاصية هو رمز معياري أكثر بكثير
  • يحتوي إصدار الخطافات على مشكلة في طرق الإنشاء حيث إذا قمت باستدعاء الخطافات بالترتيب الخاطئ ، فإن الأمور تسوء وليس هناك حقًا أي طريقة لرؤية ذلك على الفور من الكود.

هل الشفرة المعيارية هي حقًا صفقة كبيرة؟ أعني ، يمكنك بسهولة إعادة استخدام العقار الآن ، فالشفرة كلها في مكان واحد. لذا فهي في الحقيقة _ فقط_ حجة الإسهاب الآن.

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

أعني ، يمكنك بسهولة إعادة استخدام العقار الآن ، فالشفرة كلها في مكان واحد.

لا يعني وجود الكود في مكان واحد أنه قابل لإعادة الاستخدام.
هل تمانع في إنشاء عنصر واجهة مستخدم ثانوي يعيد استخدام الكود الموجود حاليًا داخل _ExampleState في عنصر واجهة مستخدم مختلف؟
مع تطور: يجب أن تدير هذه الأداة الجديدة معرف المستخدم الخاص بها داخليًا داخل حالتها ، بحيث يكون لدينا:

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;
}

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

لا يعني عدم استخدام الأشخاص لشيء ما لأنه غير رسمي أن واجهة برمجة التطبيقات سيئة.

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

حتى مع المزود نفسه ، وهو مقبول على نطاق واسع وشبه رسمي الآن ، رأيت أشخاصًا يقولون "أفضل استخدام InheritedWidgets لتجنب إضافة تبعية".

هل تمانع في إنشاء عنصر واجهة مستخدم ثانوي يعيد استخدام الكود الموجود حاليًا داخل _ExampleState في عنصر واجهة مستخدم مختلف؟

تدور الكود المعني حول الحصول على معرف المستخدم من عنصر واجهة مستخدم وتمريره إلى طريقة fetchUser. سيكون رمز إدارة تغيير معرف المستخدم محليًا في نفس الكائن مختلفًا. يبدو أن هذا جيد؟ لست متأكدًا حقًا من المشكلة التي تحاول حلها هنا.

بالنسبة للسجل ، لن أستخدم الخاصية للقيام بما تصفه ، سيبدو الأمر كما يلي:

class Example extends StatefulWidget {
  Example({ Key key }): super(key: key);

  <strong i="10">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  int _userId;
  Future<User> _future;

  void _setUserId(int newId) {
    if (newId == _userId)
      return;
    setState(() {
      _future = fetchUser(_userId);
    });
  }

  // ...code that uses _setUserId...

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: _future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

لا يعني عدم استخدام الأشخاص لشيء ما لأنه غير رسمي أن واجهة برمجة التطبيقات سيئة.

متفق.

حقيقة أن الناس لا يستخدمون شيئًا ما لكونه سيئًا هو ما يعني أن واجهة برمجة التطبيقات (API) سيئة. عندما تقول "من غير المرجح أن ينشر مؤلفو الحزم حزمًا تعتمد على الخطافات لحل المشكلات" ، فهذا يشير إلى أن الخطافات تعتمد على استخدام الأشخاص الآخرين لها لتكون مفيدة لك. API الجيد ، IMHO ، لا يصبح سيئًا إذا لم يتبناه أحد ؛ يجب أن تصمد حتى لو لم يعلم أحد عنها. على سبيل المثال ، المثال Property أعلاه لا يعتمد على الحزم الأخرى التي تستخدمه في حد ذاته.

حتى مع المزود نفسه ، وهو مقبول على نطاق واسع وشبه رسمي الآن ، رأيت أشخاصًا يقولون "أفضل استخدام InheritedWidgets لتجنب إضافة تبعية".

ما الخطأ في الأشخاص الذين يفضلون استخدام InheritedWidget؟ لا أريد فرض حل على الناس. يجب أن يستخدموا ما يريدون استخدامه. أنت تصف حرفياً عدم وجود مشكلة. الحل للأشخاص الذين يفضلون استخدام InheritedWidget هو الابتعاد عن طريقهم والسماح لهم باستخدام InheritedWidget.

. API الجيد ، IMHO ، لا يصبح سيئًا إذا لم يتبناه أحد ؛ يجب أن تصمد حتى لو لم يعلم أحد عنها. على سبيل المثال ، مثال الخاصية أعلاه لا يعتمد على الحزم الأخرى التي تستخدمه في حد ذاته.

هناك سوء فهم.

لا تكمن المشكلة في عدم استخدام الأشخاص للخطافات بشكل عام.
يتعلق الأمر بعدم قدرة المزود على استخدام الخطافات لإصلاح المشكلات لأن الخطافات ليست رسمية في حين أن المزود كذلك.


سيكون رمز إدارة تغيير معرف المستخدم محليًا في نفس الكائن مختلفًا. يبدو أن هذا جيد؟ لست متأكدًا حقًا من المشكلة التي تحاول حلها هنا.

بالنسبة للسجل ، لن أستخدم الخاصية للقيام بما تصفه ، سيبدو الأمر كما يلي:

هذا لا يجيب على السؤال. لقد طلبت هذا تحديدًا لمقارنة قابلية إعادة استخدام الكود بين الخطافات والممتلكات.

باستخدام الخطافات ، يمكننا إعادة استخدام FetchUser :

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;

  Widget build(context) {
    final user = use(FetchUser(userId));
  }
}

باستخدام الخطافات ، يمكننا إعادة استخدام FetchUser :

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

يتعلق الأمر بعدم قدرة المزود على استخدام الخطافات لإصلاح المشكلات لأن الخطافات ليست رسمية في حين أن المزود كذلك.

IMHO لن يحتاج إلى اعتماد حل جيد لمشكلة إعادة استخدام الكود من قبل المزود على الإطلاق. ستكون مفاهيم متعامدة بالكامل. هذا شيء يتحدث عنه دليل أسلوب Flutter تحت عنوان "تجنب التأمل".

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

لا يهم. نحن نحاول إثبات قابلية إعادة استخدام الكود. يمكن أن يكون fetchUser أي شيء - بما في ذلك ChangeNotifier.addListener على سبيل المثال.

يمكن أن يكون لدينا تطبيق بديل لا يعتمد على fetchUser ، وببساطة نوفر واجهة برمجة تطبيقات للقيام بجلب البيانات الضمني:

int userId;

Widget build(context) {
  AsyncSnapshot<User> user = use(ImplicitFetcher<User>(userId, fetch: () => fetchUser(userId));
}

IMHO لن يحتاج إلى اعتماد حل جيد لمشكلة إعادة استخدام الكود من قبل المزود على الإطلاق. ستكون مفاهيم متعامدة بالكامل. هذا شيء يتحدث عنه دليل أسلوب Flutter تحت عنوان "تجنب التأمل".

لهذا ذكرت أن الخطافات بدائية

كاستعارة:
package:animations يعتمد على Animation . لكن هذه ليست مشكلة ، لأن هذا جوهر بدائي.
سيكون الأمر مختلفًا إذا كان package:animations يستخدم بدلاً من ذلك شوكة Animation يحتفظ بها المجتمع

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

Hixie no وجهة نظري هي أنه إذا أصبحت الخطافات أكثر شيوعًا ، يجب أن نفكر في Flutter وتصرفه .
أشاركك كثيرًا في مخاوفك ولكن على الجانب الآخر ، تبدو القطعة ذات الخطافات أنيقة حقًا.
لن يحظر القيام بالأشياء كما كان من قبل.

لن يحظر القيام بالأشياء كما كان من قبل.

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

يتبع الناس دائمًا "الطريقة الرسمية" في القيام بالأشياء ، ولا يجب أن يكون هناك طريقتان رسميتان لاستخدام Flutter.

لا يهم. نحن نحاول إثبات قابلية إعادة استخدام الكود. يمكن أن يكون fetchUser أي شيء - بما في ذلك ChangeNotifier.addListener على سبيل المثال.

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

IMHO لن يحتاج إلى اعتماد حل جيد لمشكلة إعادة استخدام الكود من قبل المزود على الإطلاق. ستكون مفاهيم متعامدة بالكامل. هذا شيء يتحدث عنه دليل أسلوب Flutter تحت عنوان "تجنب التأمل".

لهذا ذكرت أن الخطافات بدائية

إنها راحة وليست بدائية. إذا كانوا بدائيين ، فإن السؤال "ما هي المشكلة" سيكون من الأسهل بكثير الإجابة عليه. ستقول "هنا شيء أريد أن أفعله ولا يمكنني فعله".

كاستعارة:
package:animations يعتمد على Animation . لكن هذه ليست مشكلة ، لأن هذا جوهر بدائي.
سيكون الأمر مختلفًا إذا كان package:animations يستخدم بدلاً من ذلك شوكة Animation يحتفظ بها المجتمع

يقوم التسلسل الهرمي لفئة الرسوم المتحركة بعمل شيء أساسي: فهو يقدم المؤشرات وطريقة للتحكم فيها والاشتراك فيها. بدون التسلسل الهرمي لفئة الرسوم المتحركة ، عليك أن تخترع شيئًا مثل التسلسل الهرمي لفئة الرسوم المتحركة لعمل الرسوم المتحركة. (من الناحية المثالية شيء أفضل. إنه ليس أفضل عمل لدينا.) لا تقدم الخطافات ميزة أساسية جديدة. إنه يوفر فقط طريقة لكتابة نفس الرمز بشكل مختلف. قد يكون هذا الرمز أبسط ، أو تم تحليله إلى عوامل بشكل مختلف عما يمكن أن يكون بخلاف ذلك ، لكنه ليس بدائيًا. لا تحتاج إلى إطار عمل يشبه الخطافات لكتابة التعليمات البرمجية التي تقوم بنفس الشيء الذي تقوم به التعليمات البرمجية التي تستخدم الخطافات.


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

السنانير _are_ الأوليات
إليكم خيط من دان: https://twitter.com/dan_abramov/status/1093698629708251136 يشرح ذلك. تختلف بعض الصيغ ، لكن المنطق ينطبق في الغالب على Flutter نظرًا للتشابه بين مكونات فئة React و Flutter StatefulWidgets

بشكل أكثر تحديدًا ، يمكنك التفكير في flutter_hooks أنه مزيج ديناميكي للحالة.

إذا كانوا بدائيين ، فإن السؤال "ما هي المشكلة" سيكون من الأسهل بكثير الإجابة عليه. ستقول "هنا شيء أريد أن أفعله ولا يمكنني فعله".

إنه في OP:

من الصعب إعادة استخدام منطق الدولة. إما أن ينتهي بنا الأمر مع طريقة بناء معقدة ومتداخلة بعمق أو يتعين علينا نسخ ولصق المنطق عبر أدوات متعددة.
لا يمكن إعادة استخدام هذا المنطق من خلال mixins أو الوظائف.

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

لست بحاجة إلى فصول لكتابة برنامج. لكن الفئات تسمح لك بهيكلة الكود الخاص بك وعواملها بطريقة مفيدة.
والطبقات بدائية.

نفس الشيء مع mixins ، وهي بدائية أيضًا

الخطافات هي نفس الشيء.

لماذا نحتاج الخطافات لإغلاق استدعاءات تلك الوظائف؟

لأنه عندما نحتاج إلى استدعاء هذا المنطق ليس في مكان واحد ولكن في مكانين.

لا يمكن إعادة استخدام هذا المنطق من خلال mixins أو الوظائف.

من فضلك أعطني مثالًا ملموسًا حيث يكون هذا هو الحال. حتى الآن ، كانت جميع الأمثلة التي درسناها بسيطة بدون خطافات.

حتى الآن في هذا الموضوع ، لم أر أي حل آخر غير الخطافات rousselGit التي تحل وتجعل من السهل إعادة استخدام منطق الحالة

من المؤكد أنني لم أفعل الكثير من النبال والرفرفة مؤخرًا ، لذا ربما أفتقد بعض الأشياء في عينات كود حل الملكية أعلاه ، ولكن ، هل هناك أي حلول؟ ما هي الخيارات اليوم التي لا تتطلب نسخ لصق بدلاً من إعادة الاستخدام؟
ما هي الإجابة على سؤال rousselGit :

هل تمانع في إنشاء عنصر واجهة مستخدم ثانوي يعيد استخدام الكود الموجود حاليًا داخل _ExampleState في عنصر واجهة مستخدم مختلف؟
مع تطور: يجب أن تدير هذه الأداة الجديدة معرف المستخدم الخاص بها داخليًا داخل حالتها

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

راجع للشغل ، هل SwiftUI تفعل ذلك بطريقة جديدة / أخرى ملهمة؟ أم أنها تفتقر إلى نفس إمكانية إعادة استخدام منطق الحالة أيضًا؟ لم أستخدم swiftui على الإطلاق بنفسي. ربما هو مختلف جدا؟

كل بناة أساسا. البناة هم الطريقة الوحيدة لإعادة استخدام الدولة في الوقت الحالي.
الخطافات تجعل المنشئين أكثر قابلية للقراءة وأسهل في الإنشاء


إليك مجموعة من الخطافات المخصصة أنا أو بعض العملاء التي صنعتها الشهر الماضي لمشاريع مختلفة:

  • useQuery - وهو ما يعادل خطاف ImplicitFetcher الذي قدمته سابقًا ولكنه يقوم بإجراء استعلام GraphQL بدلاً من ذلك.
  • useOnResume الذي يعطي رد اتصال لتنفيذ إجراء مخصص على AppLifecycleState.resumed دون الحاجة إلى ذلك
    انتقل إلى مشكلة إنشاء WidgetsBindingObserver
  • useDebouncedListener الذي يستمع إلى مستمع (عادةً TextField أو ScrollController) ، ولكن مع تراجع على المستمع
  • useAppLinkService الذي يسمح للأدوات بتنفيذ بعض المنطق في حدث مخصص مشابه لـ AppLifecycleState.resumed لكن مع قواعد العمل
  • useShrinkUIForKeyboard للتعامل بسلاسة مع مظهر لوحة المفاتيح. تقوم بإرجاع قيمة منطقية تشير إلى ما إذا كان يجب أن تتكيف واجهة المستخدم مع الحشوة السفلية أم لا (والتي تعتمد على الاستماع إلى نقطة التركيز البؤرية)
  • useFilter ، الذي يجمع بين useDebouncedListener و useState (رابط بدائي يعلن خاصية واحدة) لعرض عامل تصفية لشريط البحث.
  • useImplicitlyAnimated<Int/Double/Color/...> - ما يعادل TweenAnimationBuilder كخطاف

تستخدم التطبيقات أيضًا العديد من الخطافات منخفضة المستوى لمنطق مختلف.

على سبيل المثال ، بدلاً من:

Whatever whatever;

initState() {
  whatever = doSomething(widget.id);
}

didUpdateWidget(oldWidget) {
  if (oldWidget.id != widget.id)
    whatever = doSomething(widget.id);
}

إنهم يفعلون:

Widget build(context) {
  final whatever = useUnaryEvent<Whatever, int>(widget.id, (int id) => doSomething(id));
}

يؤدي هذا إلى تجنب التكرار بين initState / didUpdateWidget / didChangeDependencies .

يستخدمون أيضًا قدرًا كبيرًا من useProvider ، من Riverpod والذي قد يكون لولا ذلك StreamBuilder / ValueListenableBuilder


الجزء المهم هو أن التطبيقات المصغّرة نادرًا ما تستخدم "خطاف واحد فقط".
على سبيل المثال ، قد تفعل القطعة

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    final filter = useFilter(debounceDuration: const Duration(seconds: 2));
    final userId = useProvider(authProvider).userId;
    final chatId = useProvider(selectedChatProvider);
    final chat = useQuery(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

إنه موجز وسهل القراءة (بافتراض أن لديك معرفة أساسية بواجهة برمجة التطبيقات بالطبع).
يمكن قراءة كل المنطق من أعلى إلى أسفل - لا توجد قفزة بين الطرق لفهم الكود.
ويتم إعادة استخدام جميع الخطافات المستخدمة هنا في أماكن متعددة في قاعدة الشفرة

إذا أردنا فعل الشيء نفسه تمامًا بدون خطافات ، فسنحصل على:

class ChatScreen extends StatefulWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="20">@override</strong>
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  String filter;
  Timer timer;

  <strong i="21">@override</strong>
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  <strong i="22">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {
            return GraphQLBuilder<Chat>(
              query: ChatQuery(
                userId: auth.userId,
                chatId: chatId,
                filter: filter.value,
              ),
              builder: (context, chat, child) {
                return Column(
                  children: [
                    Searchbar(
                      onChanged: (value) {
                        timer?.cancel();
                        timer = Timer(const Duration(seconds: 2), () {
                          filter = value;
                        });
                      },
                    ),
                    Expanded(
                      child: ChatList(chat: chat),
                    ),
                  ],
                );
              },
            );
          },
        );
      },
    );
  }
}

هذا أقل قابلية للقراءة بشكل ملحوظ .

  • لدينا 10 مستويات من المسافة البادئة - 12 إذا قمنا بعمل FilterBuilder لإعادة استخدام منطق المرشح
  • منطق المرشح غير قابل لإعادة الاستخدام كما هو.

    • قد ننسى إلغاء timer عن طريق الخطأ

  • نصف طريقة build ليست مفيدة للقارئ. يصرف البناة انتباهنا عما يهم
  • لقد فقدت 5 دقائق جيدة في محاولة فهم سبب عدم ترجمة الشفرة بسبب عدم وجود قوس

بصفتي مستخدمًا لـ flutter_hooks بنفسي ، سأساهم برأيي. قبل استخدام الخطافات ، كنت سعيدًا برفرفة. لم أرَ ضرورة لشيء من هذا القبيل. بعد القراءة عنها ومشاهدة مقطع فيديو على موقع youtube ، ما زلت غير مقتنع ، لقد بدا رائعًا ، لكنني كنت بحاجة إلى بعض الممارسات أو الأمثلة لتحفيزها حقًا. لكن بعد ذلك لاحظت شيئًا. كنت أتجنب استخدام الأدوات المصغرة مهما كان الثمن ، وكان هناك الكثير من النماذج المعيارية ، وتخطي الفصل في محاولة للعثور على الأشياء. وبسبب ذلك ، قمت بنقل معظم حالتي المؤقتة إلى حل إدارة الحالة جنبًا إلى جنب مع بقية حالة التطبيق ، واستخدمت للتو عناصر واجهة مستخدم عديمة الحالة. ومع ذلك ، يتسبب هذا في اعتماد منطق الأعمال على Flutter بسرعة بسبب الاعتماد على الحصول على Navigator أو BuildContext للوصول إلى InheritedWidget s / Providers أعلى في الشجرة. لا أقول إنه كان نهجًا جيدًا لإدارة الدولة ، أعلم أنه لم يكن كذلك. لكنني فعلت أي شيء يمكنني فعله حتى لا أقلق بشأن إدارة الحالة في واجهة المستخدم.

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

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

مثل الآخرين ، لا أعرف بالضبط الدافع الرئيسي لتضمينه في Flutter SDK نفسه. ومع ذلك ، هناك فكرتان حول هذا الموضوع.

  1. من حين لآخر ، سأقوم بعمل خطاف لتسهيل استخدام الحزمة التي تحتوي على وحدات تحكم تحتاج إلى التهيئة / التخلص. (على سبيل المثال ، golden_layout ، أو zefyr ). أعتقد أن المستخدمين الآخرين الذين يستخدمون flutter_hooks سيستفيدون من مثل هذه الحزمة. ومع ذلك ، لا يمكنني تبرير نشر حزمة تحتوي حرفيًا على 1-3 وظائف. سيكون البديل هو إنشاء حزمة حوض مطبخ تحتوي على الكثير من الخطافات لمختلف الحزم التي أستخدمها ، ويمكنني بعد ذلك فقط استخدام تبعية git ، ولكن بعد ذلك سيتعين على أي شخص يستخدم هذه الحزم الأخرى + flutter_hooks الاعتماد على git in من أجل الاستفادة (وهو أمر أقل قابلية للاكتشاف ، ومن المحتمل أن يحتوي على تبعيات على الحزم التي لا يهتمون بها) ، أو على حزمة تحتوي على 3 وظائف أو أنشر حزمة garden-sink على pub.dev. تبدو كل الأفكار سخيفة وغير قابلة للاكتشاف. يمكن للمستخدمين الآخرين لـ flutter_hooks نسخ هذه الوظائف ولصقها بسهولة في التعليمات البرمجية الخاصة بهم أو محاولة اكتشاف المنطق بأنفسهم ، لكن هذا يخطئ تمامًا الهدف من مشاركة الكود / الحزم. من الأفضل أن تدخل الوظائف في الحزم الأصلية ، وليس في بعض "حزمة الامتداد". إذا كان flutter_hooks جزءًا من إطار العمل ، أو حتى مجرد حزمة مستخدمة أو مُصدرة من إطار العمل مثل characters ، فمن المرجح أن يقبل مؤلفو الحزمة الأصلية طلب سحب لخطاف بسيط وظائف ، ولن يكون لدينا فوضى من 1-3 حزم وظائف.
    إذا لم يتم اعتماد flutter_hooks بواسطة Flutter ، أتوقع مجموعة من 1-3 حزم وظائف تشوش نتائج بحث pub.dev. حقيقة أن هذه الحزم ستكون صغيرة حقًا تجعلني أتفق حقًا مع rousselGit على أن هذا بدائي. إذا لم يكن 1228 نجمة على المستودع flutter_hooks يشير إلى أنه يحل المشكلات التي ذكرها rousselGit ، فأنا لا أعرف ما هو.

  2. كنت أشاهد مقطع فيديو على youtube حول المساهمة في Flutter repo منذ أن كنت مهتمًا برؤية ما يمكنني فعله للمساعدة. أثناء مشاهدتي ، تمت إضافة الشخص الذي أنشأ الفيديو إلى العقار الجديد بسهولة إلى حد ما ، لكنه كاد أن يتكفل بتحديث dispose ، didUpdateWidget ، و debugFillProperties . إن رؤية كل تعقيدات الأداة المصغرة مرة أخرى ، ومدى سهولة تفويت شيء ما جعلني أشعر بعدم الثقة بهم مرة أخرى ، وجعلني غير متحمس للمساهمة في مستودع Flutter الرئيسي. لا أقول أنه ردعني تمامًا ، ما زلت مهتمًا بالمساهمة ، لكن يبدو أنني سأقوم بإنشاء رمز معياري يصعب صيانته ومراجعته. لا يتعلق الأمر بتعقيد كتابة الكود ، ولكن تعقيد قراءة الكود والتحقق من أنك تخلصت بشكل صحيح من الحالة المؤقتة وتهتم بها.

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

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

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

أتمنى أن يكون هذا أكثر صبرًا على هذه المشكلة - لا أفهم الخطافات بشكل أعمق بعد قراءة هذا الموضوع ، بخلاف أنها طريقة أخرى لربط عمر المستهلكات بالدولة. أنا لا أفضل هذا النهج من الناحية الأسلوبية ، وأشعر أن هناك شيئًا معيبًا بشكل أساسي إذا كان الموضع هو `` فقط خذ الوقت الكافي لكتابة تطبيق جديد بالكامل في النموذج ، ثم ستفهم سبب الحاجة إلى وضعه في إطار العمل! " - كما أشار مهندس React في هذا الخيط ، لن يكون من المستحسن حقًا استخدام Flutter ، والفوائد الموصوفة في هذا الخيط صغيرة مقارنة بتكلفة نوع إعادة الأسلاك التي تعني أنك بحاجة إلى قاعدة بيانات جديدة تمامًا لمعرفة الفائدة.

أنا بصراحة أجد صعوبة في التفكير في مثال أوضح على مساعدة المشرف.

متفق. أنا ممتن لـ Hixie لتخصيص الوقت للمشاركة في هذه المناقشة.

لا أفهم الخطافات بعمق بعد قراءة هذا الموضوع

لكي نكون منصفين ، تحاول هذه المشكلة صراحة تجنب الحديث عن الخطافات على وجه التحديد.
يتعلق الأمر بمحاولة شرح المشكلة بدلاً من الحل

هل تشعر أنها فشلت في القيام بذلك؟

يمكنني أن أشعر بالجانبين ( rousselGit و @ Hixie) هنا وأردت ترك بعض التعليقات من جانب الاستخدام / وجهة نظر إطار عمل Flutter.

يعمل أسلوب flutter_hooks تقليل النموذج المعياري كثيرًا (فقط من الأمثلة الموضحة هنا حيث يمكننا إعادة استخدام تكوينات الحالة هذه) ويقلل من التعقيد من خلال عدم الاضطرار إلى التفكير بنشاط في تهيئة / التخلص من الموارد. بشكل عام ، يقوم بعمل جيد في تحسين ودعم تدفق / سرعة التطوير ... على الرغم من أنه لا يتناسب بشكل جيد مع "جوهر" Flutter نفسه (بشكل شخصي).

مثل ما لا يقل عن 95٪ من الكود الذي أكتبه ينتج عنه أسلوب البناء ليكون تعريفًا فقط ، ولا توجد متغيرات محلية أو مكالمات خارج الشجرة الفرعية لعنصر واجهة المستخدم التي تم إرجاعها ، كل جزء المنطق موجود داخل وظائف الحالة هذه لتهيئة الموارد وتعيينها والتخلص منها وإضافتها المستمعين (في حالتي ردود فعل MobX) ومثل هذه الأشياء المنطقية. نظرًا لأن هذا أيضًا هو النهج في معظم الأحيان في Flutter نفسه ، فإنه يبدو أصليًا للغاية. يمنحك القيام بذلك أيضًا للمطور الفرصة ليكون دائمًا صريحًا ومنفتحًا بشأن ما تفعله - إنه يجبرني دائمًا على تحويل هذه الأدوات لتكون StatefulWidget وكتابة كود مشابه في initState / dispose ، ولكن أيضًا دائمًا ما يؤدي إلى تدوين ما تنوي فعله مباشرة في الأداة المستخدمة. بالنسبة لي شخصيًا ، مثل Hixie الذي ذكر نفسه بالفعل ، لا يزعجني بأي شكل من الأشكال كتابة هذا النوع من الكود المعياري ويسمح لي كمطور بتحديد كيفية التعامل معه بشكل صحيح بدلاً من الاعتماد على شيء مثل flutter_hooks للقيام بذلك بالنسبة لي ومما أدى إلى عدم فهم لماذا شيء قد تتصرف كما يفعل. يضمن استخراج عناصر واجهة المستخدم في أجزاء صغيرة أيضًا أن هذا النوع من النماذج المعيارية موجود في حالة الاستخدام التي يتم استخدامها من أجلها. مع flutter_hooks ما زلت بحاجة إلى التفكير في نوع الحالات التي تستحق كتابتها لتكون خطافًا وبالتالي إعادة استخدامها - قد ينتج عن النكهات المختلفة إما خطافات استخدام "فردية" مختلفة أو عدم وجود خطافات على الإطلاق لأنني قد عدم إعادة استخدام التكوينات كثيرًا ولكن تميل إلى كتابة المزيد من التكوينات المخصصة.

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

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

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

تم ذكر الخطافات على الرغم من أنها حل واحد وأعتقد أن rousselGit يستخدمها هنا لمحاولة شرح المشكلة / المشكلة التي ملائمة للرفرفة. مع وعرض. حتى الآن على حد علمي ، لم يتم تقديم أي حلول أخرى في هذا الموضوع على الرغم من أن ذلك يحل مشكلات إعادة الاستخدام؟

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

أقل قابلية للقراءة بشكل ملحوظ

لا يبدو أن حل الخاصية يمكن إعادة استخدامه بسهولة أو أن هذا الاستنتاج الخاطئ الذي توصلت إليه (؟) نظرًا لعدم وجود إجابة حول كيفية استخدامه من أجل:

إنشاء عنصر واجهة مستخدم ثانوي يعيد استخدام الكود الموجود حاليًا داخل _ExampleState في عنصر واجهة مستخدم مختلف؟
مع تطور: يجب أن تدير هذه الأداة الجديدة معرف المستخدم الخاص بها داخليًا داخل حالتها

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

أنا متشكك جدًا في فكرة عمل مستند تصميم في الوقت الحالي.
من الواضح أنه في الوقت الحالي ، تعارضHixie حل هذه المشكلة داخل Flutter مباشرة.

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

كان موضوع هذه القضية قراءة ممتعة للغاية ويسعدني أن أرى أن تبادل وجهات النظر ظل حضاريًا. لكنني مندهش قليلاً من المأزق الحالي.

عندما يتعلق الأمر بالخطافات ، فإن وجهة نظري هي أنه بينما لا يحتاج Flutter بالضرورة إلى حل الخطافات المحدد الذي قدمه

في ظل عدم وجود أي مقترحات أخرى قابلة للتطبيق تقدم نفس القدرات ، وبالنظر إلى حقيقة أن الخطافات لها سجل حافل في React ، وأن هناك حلًا قائمًا يمكن أن يعتمد عليه لـ Flutter ، لا أعتقد أنه سيكون كذلك اختيارًا سيئًا للقيام بذلك. لا أعتقد أن مفهوم الخطافات ، باعتباره بدائيًا مضمنًا أيضًا في Flutter SDK (أو حتى أقل منه) ، سيأخذ أي شيء بعيدًا عن Flutter. في رأيي ، لن يؤدي ذلك إلا إلى إثرائه وتسهيل تطوير تطبيقات مبهجة وقابلة للصيانة باستخدام Flutter.

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

في كثير من الأحيان عند إنشاء حزم قابلة لإعادة الاستخدام داخليًا ، فإننا نناقش ما إذا كانت الحزمة تحتاج إلى أن تكون "نقية" ، بمعنى أنها قد تعتمد فقط على Dart + Flutter SDK ، أو إذا سمحنا ببعض الحزم الأخرى فيها وإذا كان الأمر كذلك ، فما منها. حتى الموفر خرج للحزم "النقية" ، ولكن غالبًا ما يُسمح به للحزم ذات المستوى الأعلى. بالنسبة إلى التطبيق ، هناك دائمًا نفس النقاش ، أي الحزم مقبولة وأيها ليست كذلك. الموفر أخضر ، لكن شيئًا مثل الخطافات لا يزال علامة استفهام كحزمة.

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

(أظن أن الحزم المحافظة تأتي من حرقها من قبل الحزم وفوضى الاعتماد على مديري الحزم الآخرين في الماضي ، وربما لم تكن فريدة من نوعها).

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

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

والثاني هو أنه لا ينبغي أن يكون هناك سبب يمنع الناس من استخدام الخطافات فقط (أو مكتبة أخرى) لحل هذه المشكلة. الآن ، مع الخطافات على وجه التحديد ، هذا لا يعمل ، كما ناقش الناس ، لأن كتابة الخطاف مرهقة بما يكفي بحيث يرغب الناس في أن تدعم المكتبات غير ذات الصلة الخطافات. لكنني أعتقد أن الحل الجيد لهذه المشكلة لن يحتاج إلى ذلك. سيكون الحل الجيد قائمًا بذاته ولا يحتاج إلى معرفة كل مكتبة أخرى به.

لقد أضفنا مؤخرًا RestorableProperties إلى إطار العمل. سيكون من المثير للاهتمام معرفة ما إذا كان من الممكن الاستفادة منها هنا بطريقة ما ...

أوافق Hixie على وجود مشكلات خفية في واجهة برمجة التطبيقات تتطلب

Hixie لكنك توافق على المشكلة ، إذًا ليست هناك طريقة جيدة لإعادة استخدام منطق الحالة بطريقة قابلة للتكوين بين الأدوات؟ لهذا السبب بدأت في التفكير في الاستفادة من العقارات القابلة لإعادة الاستخدام بطريقة ما؟

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

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

والثاني هو أنه لا ينبغي أن يكون هناك سبب يمنع الناس من استخدام الخطافات فقط (أو مكتبة أخرى) لحل هذه المشكلة. الآن ، مع الخطافات على وجه التحديد ، هذا لا يعمل ، كما ناقش الناس ، لأن كتابة الخطاف مرهقة بما يكفي بحيث يرغب الناس في أن تدعم المكتبات غير ذات الصلة الخطافات. لكنني أعتقد أن الحل الجيد لهذه المشكلة لن يحتاج إلى ذلك. سيكون الحل الجيد قائمًا بذاته ولا يحتاج إلى معرفة كل مكتبة أخرى به.

خطافات الكتابة ليست مرهقة.
لا يزال أسهل من الحلول المتاحة الآن IMHO (لاستخدام هذه العبارة مرة أخرى 😉).
ربما أسيء تفسير ما تكتبه. لكن لا أعتقد أن أحداً قال ذلك؟
قرأته كما لو أن الناس يقدرون حقًا جميع المكاسب التي يجلبها حل الخطاف إلى الطاولة وأتمنى أن يتمكنوا من استخدامه في كل مكان. لجني كل الفوائد. نظرًا لأن الخطاف قابل لإعادة الاستخدام ، فسيكون من الرائع أن يشعر مطورو الطرف الثالث بالثقة في الترميز وشحن الخطافات الخاصة بهم دون مطالبة الجميع بكتابة أغلفة خاصة بهم. جني ثمار إعادة استخدام منطق الدولة.
أعتقد أن rousselGit و gaearon قد شرحا الشيء البدائي بالفعل. لذلك لن أدخل في ذلك.
ربما لا أحصل على هذا البيان لأنني لا أستطيع أن أرى أنه ملخص جيد لما كتبه الناس في هذا الموضوع. أنا آسف.

نأمل أن يكون هناك طريق إلى الأمام. لكني أعتقد أن الوقت قد حان للاتفاق على الأقل على أن هذه مشكلة وإما المضي قدمًا في الخروج بحلول بديلة أفضل لأن الخطافات لا يبدو أنها مطروحة على الطاولة.
أو قرر فقط تخطي حل المشكلة في قلب الرفرفة.

من الذي يقرر الطريق إلى الأمام؟
ما هي الخطوة التالية؟

هذا يبدو وكأنه مشكلة قاتلة بالنسبة لي. أفهم أنه من خلال الانضباط واتباع الوثائق ، يمكنك تجنب ذلك ولكن IMHO الحل الجيد لمشكلة إعادة استخدام الكود لن يكون به هذا الخلل.

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

السبب الحقيقي في كتابتي لهذا التعليق هو أن Flutter يستخدم لغة مجمعة بالرغم من ذلك. "Linting" ليس اختياريًا. لذلك ، إذا كان هناك محاذاة بين لغة المضيف وإطار عمل واجهة المستخدم ، فمن الممكن بالتأكيد فرض هذه المشكلة "الشرطية" لا تظهر أبدًا بشكل ثابت. لكن هذا لا يعمل إلا عندما يكون إطار عمل واجهة المستخدم قادرًا على تحفيز تغييرات اللغة (مثل Compose + Kotlin).

Hixie لكنك توافق على المشكلة ، إذًا ليست هناك طريقة جيدة لإعادة استخدام منطق الحالة بطريقة قابلة للتكوين بين الأدوات؟ لهذا السبب بدأت في التفكير في الاستفادة من العقارات القابلة لإعادة الاستخدام بطريقة ما؟

إنه بالتأكيد شيء نشأه الناس. إنه ليس شيئًا لدي تجربة عميقة معه. إنه ليس شيئًا شعرت أنه يمثل مشكلة عند كتابة تطبيقاتي الخاصة باستخدام Flutter. هذا لا يعني أنها ليست مشكلة حقيقية لبعض الناس.

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

نقطتي هي أن الحل الجيد هنا لن يتطلب من أي شخص أن يكتب أغلفة.

ما هي الخطوة التالية؟

هناك العديد من الخطوات التالية ، على سبيل المثال:

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

كان موضوع هذه القضية قراءة ممتعة للغاية ويسعدني أن أرى أن تبادل وجهات النظر ظل حضاريًا.

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

نقطتي هي أن الحل الجيد هنا لن يتطلب من أي شخص أن يكتب أغلفة.

ليس عليك كتابة أغلفة بالرغم من ذلك. ولكن قد ترغب في جني الفوائد وقابلية إعادة الاستخدام في التعليمات البرمجية الخاصة بك التي اعتدت عليها. يمكنك بالتأكيد استخدام المكتبات كما هي. إذا قمت بكتابة مواد تغليف الخطاف (إن أمكن) ، فربما لا يكون ذلك لأنك تعتقد أنها عبء ولكنه أفضل من البديل.

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

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

النقطة التي أحاول توضيحها هي أن الحل الجيد IMHO لن يتطلب _ أي شخص _ لكتابة هذا المنطق. لن يكون هناك أي منطق زائد عن الحاجة لإعادة استخدامه. على سبيل المثال ، بالنظر إلى مثال "fetchUser" من وقت سابق ، لن يضطر أي شخص إلى كتابة hook ، أو ما يعادله ، لاستدعاء وظيفة "fetchUser" ، يمكنك فقط استدعاء وظيفة "fetchUser" مباشرة. وبالمثل ، لن يحتاج "fetchUser" إلى معرفة أي شيء عن الخطافات (أو أي شيء نستخدمه) ولن تحتاج الخطافات (أو أي شيء نستخدمه) إلى معرفة أي شيء عن "fetchUser". كل ذلك مع الحفاظ على المنطق الذي تكتبه تافهًا ، كما هو الحال مع الخطافات.

القيود الحالية ناتجة عن حقيقة أن الخطافات هي تصحيح في الجزء العلوي من قيود اللغة.

في بعض اللغات ، تعتبر الخطافات بناء لغة ، مثل:

state count = 0;

return RaisedButton(
  onPressed: () => count++,
  child: Text('clicked $count times'),
)

قد يكون هذا متغيرًا من وظائف غير متزامنة / متزامنة ، والتي يمكن أن تحافظ على بعض الحالات عبر المكالمات.

لم يعد يتطلب استخدامًا غير مشروط لأنه كجزء من اللغة ، يمكننا تمييز كل متغير برقم سطره بدلاً من نوعه.

أود أن أضيف أن قيود الخطافات تشبه قيود إنشاء --track-widget-create.

تكسر هذه العلامة تعريف منشئ ثابت لعناصر واجهة المستخدم. لكن هذه ليست مشكلة لأن الأدوات المصغّرة تصريحية.

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

ربما لا يكون مثال الجالب هو المثال المثالي.
لكن useStream أو useAnimstion أو useStreamCintroller تجعل Widget Tree أكثر نظافة ويمنعك من نسيان التخلص منها أو الاعتناء بـ dudChangeDependencues.
لذلك فإن الطريقة الحالية لها على الفخاخ التي يمكنك الوقوع فيها. لذا أعتقد أن المشكلة المحتملة في تسلسل المكالمات ليست أكبر مثل تلك.
لست متأكدًا مما إذا كنت سأبدأ في كتابة الخطافات الخاصة بي ، ولكن الحصول على مجموعة من العناصر التي غالبًا ما تكون مطلوبة وجاهزة للاستخدام داخل إطار العمل سيكون أمرًا رائعًا.
سيكون مجرد طريقة بديلة للتعامل معهم.

Hixie ، آسف حقًا لعدم قدرتك على فهم ما تحاول وصفه ، ألقي باللوم على أن الوقت متأخر هنا ولكن ربما أنا فقط 😳 .. ولكن في الحل الجيد الذي وصفته ، أين قيم الدولة ، منطق عمل الدولة ومنطق أحداث العمر الذي يلف / يغلف حل المشكلة ليكون قابلاً للتكوين والمشاركة بين عناصر واجهة المستخدم؟ هل يمكنك أن توضح قليلاً ما يفعله الحل الجيد وكيف ترى أنه سيعمل بشكل مثالي؟

فقط أتدخل هنا قليلاً ، مع رؤية أن هناك إشارات حول الكياسة في هذه المناقشة. أنا شخصياً لا أشعر أن أي شخص هنا غير متحضر.

بعد قولي هذا ، أعتقد أنه من الجدير بالذكر أن هذا موضوع يهتم به الناس بشدة ، من جميع الجوانب.

  • أجابrousselGit على أسئلة المبتدئين حول إدارة الحالة على StackOverflow وعلى أداة تعقب المشكلة package:provider لسنوات حتى الآن. أنا أتابع فقط الأخير من خراطيم الإطفاء هذه ، وليس لدي سوى احترام اجتهاد وصبر ريمي.
  • يهتمHixie وآخرون في فريق
  • يهتم مطورو Flutter بشدة بإدارة الدولة ، لأن هذا ما يفعلونه في جزء كبير من وقت تطويرهم.

من الواضح أن جميع الأطراف في هذا النقاش لديهم سبب وجيه للدفاع عما يفعلونه. من المفهوم أيضًا أن الأمر يستغرق وقتًا لتوصيل الرسائل.

لذا ، سأكون سعيدًا إذا استمرت المناقشة ، إما هنا أو في شكل آخر. إذا كان بإمكاني المساعدة بأي طريقة ، كما هو الحال مع مستند رسمي ، فما عليك سوى إعلامي.

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

(بشكل منفصل ، أرغب في توجيه صيحة إلى gaearon للانضمام إلى هذه المناقشة. تجربة

@ ايمانويل لوندمان

ولكن في الحل الجيد الذي وصفته ، أين ستوجد قيم الحالة ومنطق عمل الولاية ومنطق أحداث العمر الذي يلتف / يغلف حل المشكلة ليكون قابلاً للتكوين والمشاركة بين الأدوات؟ هل يمكنك أن توضح قليلاً ما يفعله الحل الجيد وكيف ترى أنه سيعمل بشكل مثالي؟

للأسف لا يمكنني التفصيل لأنني لا أعرف. :-)

تضمين التغريدة

ربما لا يكون مثال الجالب هو المثال المثالي.
لكن useStream أو useAnimstion أو useStreamCintroller تجعل Widget Tree أكثر نظافة ويمنعك من نسيان التخلص منها أو الاعتناء بـ dudChangeDependencues.

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

ما قد يكون مفيدًا حقًا هو الخروج ببعض الأمثلة المتعارف عليها ، مثل تطبيق Flutter التجريبي الذي يحتوي على مثال حقيقي للمشكلة ، وهو تطبيق لم يتم تبسيطه بشكل مفرط من أجل العرض

ما رأيك في المثال الذي قدمته هنا؟ https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

هذا هو مقتطف رمز في العالم الحقيقي.

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

عُذْرًا ، قصدتُ أن أكون مقتطفًا من التعليمات البرمجية ، وليس كتطبيق.

أعتقد أن إحدى المشكلات المتعلقة بفكرة "تطبيق Flutter التجريبي" هي أن الأمثلة المقدمة في هذا الموضوع حقيقية جدًا.
ليست مبسطة.
حالة الاستخدام الأساسية للخطافات إذا تم تحليل الحالات الدقيقة ، مثل debounce ، أو معالجات الأحداث ، أو الاشتراكات ، أو الآثار الجانبية الضمنية - والتي يتم دمجها معًا لتحقيق منطق أكثر فائدة.

لدي بعض الأمثلة على Riverpod ، مثل https://marvel.riverpod.dev/#/ حيث يوجد كود المصدر هنا: https://github.com/rrousselGit/river_pod/tree/master/examples/marvel/lib
لكن هذا لن يكون مختلفًا تمامًا عما تم ذكره حتى الآن.

تضمين التغريدة

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

أنا في نفس القارب.
أعترف ، أنا لا أفهم حقًا المشكلات الموضحة هنا أيضًا. لا أفهم حتى ما هو "منطق الدولة" الذي يشير إليه الناس ، والذي يجب أن يكون قابلاً لإعادة الاستخدام.

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

أحب قوة الأداة StatefulWidget التي تأتي من بساطة واجهة برمجة تطبيقات دورة حياتها. يسمح لي بكتابة StatefulWidgets التي تفعل شيئًا واحدًا والقيام بذلك بمعزل عن بقية التطبيق. دائمًا ما تكون "حالة" عناصر واجهة المستخدم الخاصة بي خاصة بأنفسهم ، لذا فإن إعادة استخدام عناصر واجهة المستخدم الخاصة بي لا يمثل مشكلة ، كما أنه لا يمثل إعادة استخدام الكود.

لدي مشكلتان في الأمثلة المعروضة هنا ، والتي تتماشى إلى حد ما مع نقاطك:

  • يبدو أن إنشاء عناصر واجهة مستخدم متعددة الحالة باستخدام "منطق الحالة" نفسه أمر خاطئ ويتعارض مع فكرة وجود عناصر واجهة مستخدم مستقلة بذاتها. لكن مرة أخرى ، أنا في حيرة من أمري بشأن ما يقصده الناس بـ "منطق الدولة" المشترك.
  • لا يبدو أن الخطافات تفعل أي شيء لا يمكنني فعله بالفعل باستخدام مفاهيم البرمجة الأساسية والسهام البسيطة (مثل الوظائف)
  • يبدو أن المشكلات مرتبطة أو ناجمة عن نمط معين من البرمجة ، وهو أسلوب يبدو أنه يفضل "حالة عالمية قابلة لإعادة الاستخدام".
  • إن تجريد سطرين من الكود يفوح برائحة "تحسين الشفرة المبكرة" ويضيف تعقيدًا من أجل حل مشكلة لا علاقة لها بإطار العمل وكل ما يتعلق بكيفية استخدام الأشخاص له.

هذا أقل قابلية للقراءة بشكل ملحوظ .

  • لدينا 10 مستويات من المسافة البادئة - 12 إذا قمنا بعمل FilterBuilder لإعادة استخدام منطق المرشح
  • منطق المرشح غير قابل لإعادة الاستخدام كما هو.

    • قد ننسى إلغاء timer عن طريق الخطأ
  • نصف طريقة build ليست مفيدة للقارئ. يصرف البناة انتباهنا عما يهم
  • لقد فقدت 5 دقائق جيدة في محاولة فهم سبب عدم ترجمة الشفرة بسبب عدم وجود قوس

يعد المثال الخاص بك بمثابة عرض لكيفية تقديم الموفر المطول ولماذا يعد إساءة استخدام InheritedWidgets لكل شيء أمرًا سيئًا ، وليس أي مشكلة حقيقية في Flutter's StatelessWidget و State lifeycle api.

rousselGit آسف إذا لم أكن واضحًا. كان الاقتراح الذي قدمته أعلاه هو على وجه التحديد إنشاء تطبيقات Vanilla Flutter (باستخدام StatefulWidget ، إلخ) الواقعية وتعرض المشكلات التي تصفها ، حتى نتمكن بعد ذلك من تقديم مقترحات بناءً على تطبيق حقيقي كامل. الأمثلة الملموسة التي ناقشناها هنا ، مثل مثال "fetchUser" ، تنتهي دائمًا بمناقشة على غرار "حسنًا يمكنك التعامل مع هذه الحالة مثل هذه وستكون بسيطة ولن تحتاج إلى خطافات". من خلال "حسنًا ، هذا مفرط في التبسيط ، في العالم الحقيقي تحتاج إلى خطافات". لذا فإن وجهة نظري هي ، دعنا ننشئ مثالًا حقيقيًا من العالم الحقيقي _هل _ يحتاج حقًا إلى الخطافات ، وهذا ليس مفرطًا في التبسيط ، وهذا يوضح صعوبة إعادة استخدام الكود ، وبهذه الطريقة يمكننا معرفة ما إذا كان من الممكن تجنب هذه المشكلات دون استخدام أي كود جديد ، أو إذا احتجنا إلى رمز جديد ، وفي الحالة الأخيرة ، ما إذا كان يجب أن يكون على شكل خطافات أو إذا كان بإمكاننا إيجاد حل أفضل.

لا أفهم حتى ما هو "منطق الدولة" الذي يشير إليه الناس ، والذي يجب أن يكون قابلاً لإعادة الاستخدام.

عادل بما يكفي
سيكون المنطق الموازي للحالة هو منطق واجهة المستخدم ، وما تجلبه Widgets إلى الجدول.

يمكننا تقنيًا إزالة طبقة القطعة. في هذه الحالة ، ما يتبقى هو RenderObjects.

على سبيل المثال ، يمكن أن يكون لدينا عداد بسيط:

var counter = 0;

final counterLabel = RenderParagraph(
  TextSpan(text: '$counter'),
  textDirection: TextDirection.ltr,
);

final button = RenderPointerListener(
  onPointerUp: (_) {
    counter++;
    counterLabel.text = TextSpan(text: '$counter');
  },
  child: counterLabel,
);

هذا ليس بالضرورة معقدًا. لكنها عرضة للخطأ. لدينا نسخة مكررة من عرض counterLabel

مع الحاجيات لدينا:

class _CounterState exends State {
  int counter = 0;

  Widget build(context ) {
    return GestureDetector(
      onTap: () => setState(() => counter++);
      child: Text('$counter'),
    );
  }
}

الشيء الوحيد الذي فعله هذا هو تحليل المنطق Text ، بجعله معبرًا.
إنه تغيير بسيط. ولكن على قاعدة بيانات كبيرة ، يعد هذا تبسيطًا مهمًا .

الخطافات تفعل نفس الشيء بالضبط.
ولكن بدلاً من Text ، تحصل على خطاطيف مخصصة لمنطق الحالة. والتي تشمل المستمعين ، والرد ، وتقديم طلبات HTTP ، ...


يعد المثال الخاص بك بمثابة عرض لكيفية تقديم الموفر المطول ولماذا يعد إساءة استخدام InheritedWidgets لكل شيء أمرًا سيئًا ، وليس أي مشكلة حقيقية في Flutter's StatelessWidget و State lifeycle api.

هذا لا علاقة له بالمزود (هذا الرمز لا يستخدم الموفر بعد كل شيء).
إذا كان هناك أي شيء ، فإن المزود لديه أفضل لأنه يحتوي على context.watch بدلاً من Consumer .

سيكون الحل القياسي هو استبدال Consumer بـ ValueListenableBuilder - مما يؤدي إلى نفس المشكلة بالضبط.

أوافق Hixie ، أعتقد أننا بحاجة إلى مقارنات جنبًا إلى جنب للحكم على فعالية Flutter مقابل الخطافات. سيساعد هذا أيضًا في إقناع الآخرين بما إذا كانت الخطافات أفضل أم لا ، أو ربما يكون حل آخر أفضل إذا تم إنشاء تطبيق الفانيليا باستخدام هذا الحل الثالث. كان مفهوم تطبيق الفانيليا هذا موجودًا منذ فترة ، حيث تُظهر أشياء مثل TodoMVC الفرق بين مختلف الأطر الأمامية ، لذلك فهي ليست جديدة بالضرورة. يمكنني المساعدة في إنشاء هذه الأمثلة على التطبيقات.

تضمين التغريدة
سأكون على استعداد للمساعدة.
أعتقد أن التطبيق النموذجي في flutter_hooks repo ربما يعرض العديد من الخطافات المختلفة وما يسهله / المشاكل التي يحلونها ، وسيكون نقطة انطلاق جيدة.

أعتقد أيضًا أنه يمكننا أيضًا استخدام بعض الأمثلة والأساليب المقدمة في هذه المسألة.

تحديث: الكود هنا ، https://github.com/TimWhiting/local_widget_state_approaches
لست متأكدًا من الاسم الصحيح للمستودع ، لذلك لا تفترض أن هذه هي المشكلة التي نحاول حلها. لقد أنجزت تطبيق العداد الأساسي في حالة وخطافات. ليس لدي الكثير من الوقت في وقت لاحق الليلة ، لكنني سأحاول إضافة المزيد من حالات الاستخدام التي توضح بشكل أكبر ما قد يكون المشكلة. لمن يريد المساهمة ، الرجاء طلب الوصول.

الأمثلة الملموسة التي ناقشناها هنا ، مثل مثال "fetchUser" ، تنتهي دائمًا بمناقشة على غرار "حسنًا يمكنك التعامل مع هذه الحالة مثل هذه وستكون بسيطة ولن تحتاج إلى خطافات". من خلال "حسنًا ، هذا مفرط في التبسيط ، في العالم الحقيقي تحتاج إلى خطافات".

أنا أعترض. لا أعتقد أنني رأيت مثل هذا "يمكنك التعامل مع هذه الحالة مثل هذه" ووافقت على أن الكود الناتج كان أفضل من متغير الخطاف.

كانت وجهة نظري دائمًا أنه بينما يمكننا القيام بالأشياء بشكل مختلف ، فإن الكود الناتج يكون عرضة للخطأ و / أو أقل قابلية للقراءة.
ينطبق هذا أيضًا على fetchUser

الخطافات تفعل نفس الشيء بالضبط.
ولكن بدلاً من Text ، تحصل على خطاطيف مخصصة لمنطق الحالة. والتي تشمل المستمعين ، والرد ، وتقديم طلبات HTTP ، ...

كلا ، ما زلت لا أفهم ما يفترض أن يكون عليه منطق الحالة المشتركة هذا. أعني أن لدي العديد من الأدوات التي تقرأ من قاعدة بيانات في أساليب "initState / didUpdateDependency" الخاصة بهم ، لكن لا يمكنني العثور على عنصرين يقومان بعمل نفس الاستعلام بالضبط وبالتالي فإن "منطقهم" ليس هو نفسه.

استخدام مثال تقديم طلب HTTP. بافتراض أن لديّ "makeHTTPRequest (عنوان url ، بارامترات)" في مكان ما في فئة الخدمة الخاصة بي والتي تحتاج بعض أدواتي إلى استخدامها ، فلماذا أستخدم خطافًا بدلاً من استدعاء الطريقة مباشرةً متى احتجت إليها؟ كيف يختلف استخدام الخطافات عن استدعاءات الطريقة العادية في هذه الحالة؟

مستمعون. ليس لدي عناصر واجهة تستمع إلى نفس الأشياء. كل عنصر من عناصر واجهة المستخدم الخاصة بي مسؤول عن الاشتراك في كل ما يحتاجون إليه والتأكد من إلغاء الاشتراك. قد تكون الخطافات عبارة عن سكر نحوي لمعظم الأشياء ، ولكن نظرًا لأن عناصر واجهة المستخدم الخاصة بي لن تستمع إلى نفس مجموعة العناصر ، فيجب أن تكون الخطافات "معلمة" بطريقة ما. مرة أخرى ، كيف تختلف الخطافات عن الوظيفة القديمة البسيطة؟


هذا لا علاقة له بالمزود (هذا الرمز لا يستخدم الموفر بعد كل شيء).
إذا كان هناك أي شيء ، فإن المزود لديه أفضل لأنه يحتوي على context.watch بدلاً من Consumer .

هاه؟ كان المثال المضاد لما يفترض أن تحله HookWidget "ChatScreen" هو:

<strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {

كيف هذا لا علاقة له بالمزود؟ انا مرتبك. لست خبيرًا في الموفر ، لكن هذا يبدو بالتأكيد مثل رمز يستخدم المزود.

أود أن أصر على حقيقة أن هذه القضية لا تتعلق بالدول المعقدة.
إنها عبارة عن زيادات صغيرة يمكن تطبيقها على قاعدة الكود بأكملها.

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

توصيتي بدلاً من ذلك بمقارنة المقتطفات الصغيرة جنبًا إلى جنب مثل ImplicitFetcher ، و __وضوعيًا_ تحديد الرمز الأفضل باستخدام المقاييس القابلة للقياس ، والقيام بذلك لمجموعة متنوعة من المقتطفات الصغيرة.


كيف هذا لا علاقة له بالمزود؟ انا مرتبك. لست خبيرًا في الموفر ، لكن هذا يبدو بالتأكيد مثل رمز يستخدم المزود.

هذا الرمز ليس من Provider ولكن من مشروع مختلف لا يستخدم InheritedWidgets.
لا يحتوي الموفر Consumer provider معلمة

وكما ذكرت ، يمكنك استبدال Consumer -> ValueListenableBuilder / StreamBuilder / BlocBuilder / Observer / ...

في طرق "initState / didUpdateDependency" الخاصة بهم ، لكن لا يمكنني العثور على ودجتين تقومان بعمل نفس الاستعلام بالضبط ، وبالتالي فإن "منطقهم" ليس هو نفسه.

إن منطق الحالة الذي نريد إعادة استخدامه ليس "إجراء استعلام" ولكن "القيام بشيء ما عندما يتغير x". قد يتغير "افعل شيئًا" ، لكن "عندما يتغير س" أمر شائع

مثال ملموس:
قد نرغب في أن يقوم عنصر واجهة المستخدم بإجراء طلب HTTP كلما تغير المعرف الذي يستقبله.
نريد أيضًا إلغاء الطلبات المعلقة باستخدام عملية إلغاء الأمر package:async .

الآن ، لدينا ودجتان تريدان فعل الشيء نفسه تمامًا ، ولكن بطلب HTTP مختلف.
في النهاية لدينا:

CancelableOperation<User> pendingUserRequest;

initState() {
  pendingUserRequest = fetchUser(widget.userId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.userId) {
      pendingUserRequest.cancel();
      pendingUserRequest = fetchUser(widget.userId);
  }
}

dispose() {
  pendingUserRequest.cancel();
}

ضد:

CancelableOperation<Message> pendingMessageRequest;

initState() {
  pendingMessageRequest = fetchMessage(widget.messageId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.messageId) {
      pendingMessageRequest.cancel();
      pendingMessageRequest = fetchMessage(widget.messageId);
      message = pendingMessageRequest.value;
  }
}

dispose() {
  pendingMessageRequest.cancel();
}

الاختلاف الوحيد هو أننا قمنا بتغيير fetchUser بـ fetchMessage . المنطق 100٪ هو نفسه. لكن لا يمكننا إعادة استخدامه ، فهو عرضة للخطأ.

باستخدام الخطافات ، يمكننا تحليل ذلك في خطاف useUnaryCancelableOperation .

مما يعني أنه باستخدام نفس الحاجتين ستعملان بدلاً من ذلك:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

ضد

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

في هذا السيناريو ، تتم مشاركة كل المنطق المتعلق بإجراء الطلب وإلغائه. لم يتبق لنا سوى فرق ذي مغزى ، وهو fetchUser مقابل fetchMessage .
يمكننا حتى إنشاء حزمة من هذا useUnaryCancelableOperation ، والآن يمكن للجميع إعادة استخدامها في تطبيقهم.

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

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

الاختلاف الوحيد هو أننا قمنا بتغيير fetchUser بـ fetchMessage . المنطق 100٪ هو نفسه. لكن لا يمكننا إعادة استخدامه ، فهو عرضة للخطأ.

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

مرة أخرى ، لمجرد أن شيئًا ما هو نمط شائع ، فهذا لا يعني أنك بحاجة إلى إنشاء ميزة جديدة له. بالإضافة إلى ذلك ، إذا كنت ترغب في تقليل الكود المتكرر في هذه الحالة ، يمكنك فقط توسيع فئة StatefulWidget * وتجاوز أساليب initstate / didUpdateWidget مع وحدات البت الشائعة.

باستخدام الخطافات ، يمكننا تحليل ذلك في خطاف useUnaryCancelableOperation .

مما يعني أنه باستخدام نفس الحاجتين ستعملان بدلاً من ذلك:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

ضد

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

في هذا السيناريو ، تتم مشاركة كل المنطق المتعلق بإجراء الطلب وإلغائه. لم يتبق لنا سوى فرق ذي مغزى ، وهو fetchUser مقابل fetchMessage .
يمكننا حتى إنشاء حزمة من هذا useUnaryCancelableOperation ، والآن يمكن للجميع إعادة استخدامها في تطبيقهم.

أنا آسف ولكن هذا أمر مؤكد لا مني. بصرف النظر عن حقيقة أنه يحفظ قدرًا ضئيلًا فقط من تكرار الكود ، فإن "تحليل" الكود الذي ينتمي من الناحية المفاهيمية إلى دورتَي دورة حياة "initState" و "update" ، في طريقة build يعد أمرًا مهمًا.

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

إذا كان هذا هو الحال حقًا ، فأعتقد أنه يجب علينا إغلاق هذا الخطأ

Hixie من فضلك لا. يهتم الناس بهذه القضية. لقد تحدثت معك على reddit عن نفس الشيء ، ولكن في سياق SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

لا يتعلق الأمر بالخطافات ، بل يتعلق بطريقة ما بتحسين عناصر واجهة المستخدم ذات الحالة. الناس ببساطة يكرهون كتابة النموذج المعياري. بالنسبة للمطورين الذين يكتبون كود SwiftUI ، الذين اعتادوا على RAII ونسخ دلالات طرق العرض ، فإن إدارة المستهلكات يدويًا تبدو متوقفة.

لذلك أشجع فريق flutter على النظر إلى هذا على الأقل على أنه مشكلة والتفكير في الحلول / التحسينات البديلة.

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

أنا حقًا لا أفهم الدفع لمزيد من الأمثلة هنا. من الواضح على وجهه.

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

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

تمتلئ Flutter بهذه الحالات التي نحتاج فيها إلى تكرار أنفسنا باستمرار ، وإعداد وتمزيق حالة الكائنات الصغيرة ، والتي تخلق جميع أنواع الفرص للأخطاء التي لا تحتاج إلى الوجود ، ولكنها موجودة ، لأنه لا يوجد حاليًا نهج أنيق مشاركة هذا الإعداد الروتيني / teardown / sync logic.

يمكنك رؤية هذه المشكلة في أي حالة _حرفياً تحتوي على مرحلة إعداد وتفكيك ، أو بها بعض عناصر ربط دورة الحياة التي تحتاج دائمًا إلى الارتباط بها.

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

szotp لم أفعل ... أفضل أن ننشئ تطبيقًا أو أكثر من التطبيقات الأساسية التي توضح المشكلة حتى نتمكن من تقييم الحلول. كنت سأفعل ذلك بنفسي لكنني لا أفهم بالضبط ما الذي نحاول حله ، لذا فأنا الشخص الخطأ الذي يفعل ذلك.

ستساعدنا تطبيقات

esDotDev لقد ناقشنا حالات مثل هذه في هذا الخطأ حتى الآن ، ولكن في كل مرة لدينا ، يتم رفض الحلول بخلاف الخطافات لأنها لا تحل بعض المشكلات التي لم يتم تضمينها في المثال الذي تناوله الحل. ومن ثم ، لا يبدو أن الوصف البسيط للمشكلات كافٍ لمعرفة المدى الكامل. على سبيل المثال ، ربما يمكن حل قضية "12 متحكمًا متحركًا" من خلال مجموعة من وحدات التحكم في الرسوم المتحركة والميزات الوظيفية في Dart. قد يكون TweenAnimationBuilder حلاً آخر. لا يتضمن أي من هذين الخطافين. لكنني متأكد من أنني إذا اقترحت ذلك ، سيشير شخص ما إلى شيء فاتني ويقول "إنه لا يعمل بسبب ..." ويطرح هذه المشكلة (الجديدة ، في سياق المثال). ومن هنا تأتي الحاجة إلى تطبيق أساسي نتفق عليه جميعًا يغطي الانتشار الكامل للمشكلة.

إذا كان أي شخص يرغب في دفع هذا إلى الأمام ، أعتقد حقًا أن أفضل طريقة للقيام بذلك هي ما وصفته أعلاه (https://github.com/flutter/flutter/issues/51752#issuecomment-670249755 و https://github.com / flutter / flutter / issues / 51752 # issuecomment-670232842). سيعطينا ذلك نقطة بداية يمكننا أن نتفق جميعًا على أنها تمثل حجم المشكلة التي نحاول حلها ؛ ثم، يمكننا تصميم الحلول التي تعالج تلك المشاكل بطرق عنوان كل الرغبات (على سبيل المثالrrousselGit الصورة الحاجة لإعادة استخدامها،Rudiksz "الحاجة الصورة لطرق بناء نظيفة، الخ)، والأهم من ذلك يمكننا تقييم هذه الحلول في سياق التطبيقات الأساسية.

أعتقد أننا يمكن أن نتفق بسهولة إلى حد ما على مشكلته:
_لا توجد طريقة أنيقة لمشاركة مهام الإعداد / التفكيك المرتبطة بأشياء مثل Streams و AnimatorControllers وما إلى ذلك ، وهذا يؤدي إلى الإسهاب غير الضروري ، وفتحات للأخطاء ، وانخفاض قابلية القراءة ._

هل هناك من لا يتفق مع ذلك؟ ألا يمكننا أن نبدأ من هناك ونتقدم بحثًا عن حل؟ علينا أن نتفق على أن هذه مشكلة أساسية أولاً ، ويبدو أننا ما زلنا لا نفعل ذلك.

بينما أكتب هذا ، أدركت أنه يطابق تمامًا اسم المشكلة ، وهو اسم مفتوح ويترك مجالًا للمناقشة:
" إعادة استخدام منطق الحالة إما مطول جدًا أو صعب للغاية "

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

Hixie من فضلك لا. يهتم الناس بهذه القضية. لقد تحدثت معك على reddit عن نفس الشيء ، ولكن في سياق SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

يمكن تكرار مثال SwiftUI الخاص بك في dart في بضعة أسطر من التعليمات البرمجية ، ببساطة عن طريق توسيع فئة StatefulWidget.

لديّ StatefulWidgets لا تشترك في أي إخطارات و / أو تجري أي مكالمات خارجية ، وفي الواقع معظمها من هذا القبيل. لدي حوالي 100 أداة مخصصة (على الرغم من أنها ليست كلها ذات حالة) ، وربما يكون لدى 15 منها أي نوع من "منطق الحالة المشتركة" كما هو موضح في الأمثلة هنا.

على المدى الطويل ، تعد كتابة بضعة أسطر من الكود (المعروف أيضًا باسم boilerplate) مقايضة صغيرة لتجنب النفقات الزائدة غير الضرورية. ومرة أخرى ، يبدو أن هذه المشكلة المتمثلة في الاضطرار إلى تنفيذ أساليب initState / didUpdate مبالغ فيها. عندما أقوم بإنشاء عنصر واجهة مستخدم يستخدم أيًا من الأنماط الموضحة هنا ، ربما أقضي أول 5-10 دقائق في "تنفيذ" أساليب دورة الحياة ثم بعد ذلك بضعة أيام في الواقع في كتابة وصقل الأداة نفسها مع عدم لمس أساليب دورة الحياة المذكورة مطلقًا. إن مقدار الوقت الذي أقضيه في كتابة ما يسمى رمز الإعداد / التمزيق المعياري ضئيل للغاية مقارنة برمز تطبيقي.

كما قلت ، حقيقة أن StatefulWidgets تضع افتراضات قليلة جدًا حول ما يتم استخدامها من أجله هو ما يجعلها قوية وفعالة.

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

esDotDev أوافق على أن هذه مشكلة يواجهها بعض الأشخاص ؛ حتى أنني اقترحت بعض الحلول في وقت سابق في هذه المشكلة (ابحث عن الإصدارات المختلفة الخاصة بي من فئة Property ، قد يتم دفنها الآن لأن GitHub لا ترغب في عرض جميع التعليقات). تكمن الصعوبة في رفض هذه المقترحات لأنها لم تحل مشكلات محددة (على سبيل المثال ، في حالة واحدة ، لم تتعامل مع إعادة التحميل السريع ، وفي حالة أخرى ، لم تتعامل مع didUpdateWidget). ثم قدمت المزيد من المقترحات ، ولكن تم رفض تلك المقترحات مرة أخرى لأنهم لم يتعاملوا مع شيء آخر (نسيت ماذا). هذا هو السبب في أنه من المهم أن يكون هناك نوع من الأساس الذي نتفق عليه يمثل المشكلة _ الأساسية _ حتى نتمكن من إيجاد حلول لهذه المشكلة.

الهدف لم يتغير أبدا. الانتقادات التي وجهت هي أن الحلول المقترحة تفتقر إلى المرونة. لم يستمر أي منهم في العمل خارج المقتطف الذي تم تنفيذه من أجله.

هذا هو السبب في أن العنوان في هذا العدد يذكر "صعب": لأنه لا توجد حاليًا مرونة في الطريقة التي نحل بها المشكلات حاليًا.


هناك طريقة أخرى للنظر إليها وهي:

تناقش هذه المشكلة بشكل أساسي أننا بحاجة إلى تنفيذ طبقة عنصر واجهة مستخدم لمنطق الحالة.
الحلول المقترحة هي "ولكن يمكنك القيام بذلك باستخدام RenderObjects".

_ على المدى الطويل ، تعد كتابة بضعة أسطر من التعليمات البرمجية (المعروفة أيضًا باسم boilerplate) مقايضة صغيرة لتجنب النفقات الزائدة غير الضرورية.

زوج الصئبان مع هذا البيان:

  1. إنها ليست بضعة أسطر حقًا ، إذا أخذت الأقواس ، وتباعد الأسطر overides ، وما إلى ذلك في Thing.DOTween() ). 15 أمر سخيف.
  1. لا يتعلق الأمر بالكتابة ، على الرغم من أن هذا يمثل ألمًا. يتعلق الأمر بسخافة وجود فئة 50 سطرًا ، حيث 30 سطرًا منها عبارة عن لوحة مرجعية عن ظهر قلب. تشويشها. يتعلق الأمر بحقيقة أنك إذا _لا_ تكتب النموذج المعياري ، فلا توجد تحذيرات أو أي شيء ، لقد أضفت خطأ للتو.
  2. لا أرى أي نفقات إضافية تستحق المناقشة بشيء مثل الخطافات. نحن نتحدث عن مجموعة من الكائنات ، مع حفنة من fxns على كل منها. في Dart ، وهو سريع جدًا. هذا هو المنظمة البحرية الدولية للرنجة الحمراء.

تضمين التغريدة

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

تمديد الحاجيات. مثل الطريقة التي يقوم بها ValueNotifier بتوسيع ChangeNotifier لتبسيط نمط الاستخدام الشائع ، يمكن للجميع كتابة نكهاتهم الخاصة من StatelessWidgets لحالات الاستخدام الخاصة بهم.

نعم ، أوافق على أن هذا نهج فعال ، لكنه يترك شيئًا مرغوبًا فيه. إذا كان لدي رسام رسوم متحركة واحد ، فيمكنني استخدام TweenAnimationBuilder. رائع ، لا يزال مثل 5 أسطر من التعليمات البرمجية ، ولكن أيا كان. إنه يعمل ... ليس سيئًا للغاية. ولكن إذا كان لدي 2 أو 3؟ أنا الآن في جحيم متداخل ، إذا كان لدي cpl كائنات أخرى ذات حالة لسبب ما ، حسنًا ، كل شيء يصبح نوعًا ما فوضى من المسافة البادئة ، أو أقوم بإنشاء بعض عناصر واجهة المستخدم المحددة للغاية التي تضم مجموعة عشوائية من الإعداد والتحديث والتفكيك منطق.

تمديد الحاجيات. مثل الطريقة التي يقوم بها ValueNotifier بتوسيع ChangeNotifier لتبسيط نمط الاستخدام الشائع ، يمكن للجميع كتابة نكهاتهم الخاصة من StatelessWidgets لحالات الاستخدام الخاصة بهم.

يمكنك تمديد فئة أساسية واحدة فقط في كل مرة. هذا لا مقياس

Mixins هي المحاولة المنطقية التالية. ولكن كما يذكر البروتوكول الاختياري ، فإنها لا تتوسع أيضًا.

تضمين التغريدة

أو أقوم بإنشاء بعض عناصر واجهة المستخدم المحددة جدًا التي تضم مجموعة عشوائية من منطق الإعداد والتحديث والتفكيك.

نوع من عناصر واجهة المستخدم التي يجب أن تقوم بإعداد 3-4 أنواع من AnimationControllers يبدو وكأنه حالة استخدام محددة للغاية ولا ينبغي أن يكون دعم التجميع العشوائي لمنطق الإعداد / التفكيك جزءًا من إطار العمل. في الواقع ، هذا هو سبب الكشف عن طرق initState / didUpdateWidget في المقام الأول ، لذا يمكنك القيام بمجموعتك العشوائية من الإعداد وفقًا لرغبة قلبك.

أطول طريقة لـ initState هي 5 أسطر من التعليمات البرمجية ، ولا تعاني عناصر واجهة المستخدم الخاصة بي من التداخل المفرط ، لذلك لدينا بالتأكيد احتياجات وحالات استخدام مختلفة. أو أسلوب تطوير مختلف.

تضمين التغريدة

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

إذا كان الحل المقترح يشبه حزمة flutter_hooks فهذا غير صحيح تمامًا. نعم ، من الناحية المفاهيمية ، إنها مصفوفة بها وظائف ، لكن التنفيذ ليس قريبًا من التافه أو الكفاءة.

أعني ، قد أكون مخطئًا ، لكن يبدو أن HookElement تتحقق من ما إذا كان يجب أن تعيد بناء نفسها في طريقة البناء الخاصة بها ؟!
كما أن التحقق من تحديد حالة الخطافات أو إعادة تهيئتها أو التخلص منها في كل إنشاء عنصر واجهة مستخدم يبدو وكأنه عبء كبير. إنه ليس على ما يرام ، لذلك آمل أن أكون مخطئًا.

هل من المنطقي أخذ أحد أمثلة هندسة brianegan باعتباره تطبيقًا

إذا جاز لي التدخل هنا ، لست متأكدًا مما إذا كان قد قيل بالفعل. لكن في React ، لا نفكر حقًا في دورة الحياة باستخدام الخطافات ، وقد يبدو ذلك مخيفًا إذا كنت معتادًا على بناء المكونات / الأدوات ، ولكن إليكم سبب عدم أهمية دورة الحياة حقًا.

في معظم الأوقات ، عندما تقوم ببناء مكونات / أدوات مع حالة أو إجراءات يجب اتخاذها بناءً على الدعائم ، فأنت تريد أن يحدث شيء ما عندما تتغير هذه الحالة / الدعائم (على سبيل المثال ، كما رأيت في هذا الموضوع ، سترغب في إعادة -قم بإحضار تفاصيل المستخدم عند تغيير الخاصية userId). عادةً ما يكون من الطبيعي التفكير في ذلك على أنه "تأثير" لتغيير معرف المستخدم ، بدلاً من اعتباره شيئًا يحدث عندما تتغير جميع عناصر عنصر واجهة المستخدم.

نفس الشيء بالنسبة للتنظيف ، عادةً ما يكون من الطبيعي جدًا التفكير في "أحتاج إلى تنظيف هذه الحالة / المستمع / وحدة التحكم عندما تتغير هذه الخاصية / الحالة" بدلاً من "أحتاج إلى تذكر تنظيف X عندما تتغير جميع الدعائم / الحالة أو عندما يتم تدمير المكون بأكمله ".

لم أكتب Flutter منذ فترة ، لذلك لا أحاول أن أبدو وكأنني أعرف المناخ الحالي أو القيود التي قد يفرضها هذا النهج على عقلية Flutter الحالية ، فأنا منفتح على الآراء المختلفة. أعتقد فقط أن الكثير من الأشخاص الذين ليسوا على دراية بخطافات React لديهم نفس الارتباك الذي شعرت به عندما تعرفت عليهم لأن عقليتي كانت متأصلة جدًا في نموذج دورة الحياة.

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

https://github.com/TimWhiting/local_widget_state_approaches

يتشابه تكوين Jetpack أيضًا مع الخطافات ، والتي تمت مقارنتها بالتفاعل هنا .

تضمين التغريدة شكرا لك.

تضمين التغريدة
لا ينبغي أن تكون حالة الاستخدام المحددة للغاية ودعم التجميع العشوائي لمنطق الإعداد / التفكيك جزءًا من إطار العمل.

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

أطول طريقة لـ initState هي 5 أسطر من التعليمات البرمجية ، ولا تعاني عناصر واجهة المستخدم الخاصة بي من التداخل المفرط ، لذلك لدينا بالتأكيد احتياجات وحالات استخدام مختلفة. أو أسلوب تطوير مختلف.

لي ايضا. لكنها initState () + التخلص () + didUpdateDependancies () ، وقد يتسبب عدم وجود أي من الأخيرين في حدوث أخطاء.

أعتقد أن المثال المتعارف عليه سيكون شيئًا مثل: اكتب عرضًا يستخدم وحدة تحكم واحدة واثنان من وحدات التحكم في الرسوم المتحركة.

لديك 3 خيارات بقدر ما أستطيع رؤيته:

  1. أضف 30 سطرًا أو نحو ذلك من النموذج المعياري إلى فصلك ، وبعض الخلطات. وهي ليست مطولة فحسب ، بل يصعب متابعتها في البداية.
  2. استخدم 2 TweenAnimationBuilders و StreamBuilder ، لحوالي 15 مستوى من المسافة البادئة ، قبل أن تصل إلى كود العرض الخاص بك ، ولا يزال لديك الكثير من النماذج المعيارية للدفق.
  3. أضف 6 أسطر من الكود غير ذي المسافات البادئة في الجزء العلوي من build () ، لتحصل على 3 كائنات فرعية ذات حالة ، وحدد أي كود init / تدمير مخصص

ربما يكون هناك خيار رابع وهو SingleStreamBuilderDoubleAnimationWidget ، ولكن هذا مجرد شيء عملي للمطورين ومزعج بشكل عام.

وتجدر الإشارة أيضًا إلى أن الحمل المعرفي 3 أقل بكثير من الحمل المعرفي 2 لمطور جديد. معظم المطورين الجدد لا يعرفون حتى أن TweenAnimationBuilder موجود ، وببساطة فإن تعلم مفهوم SingleTickerProvider هو مهمة في حد ذاته. مجرد قول "أعطني رسامًا للرسوم المتحركة من فضلك" ، هو أسلوب أسهل وأكثر قوة.

سأحاول ترميز شيء ما اليوم.

2. استخدم 2 TweenAnimationBuilders و StreamBuilder ، لحوالي 15 مستوى من المسافة البادئة ، قبل أن تصل إلى كود العرض الخاص بك ، ولا يزال لديك الكثير من النماذج المعيارية للتيار.

حق. اعرض لنا مثالًا حقيقيًا من الكود يستخدم 15 مستوى من المسافة البادئة.

كيف يؤدي استبدال 30 سطرًا من التعليمات البرمجية بستة أسطر + مئات الأسطر في المكتبة إلى تقليل الحمل المعرفي؟ نعم ، يمكنني فقط تجاهل "السحر" الذي تقوم به المكتبة ، ولكن ليس قواعدها. على سبيل المثال ، تخبرك حزمة الخطافات بعبارات لا لبس فيها أنه يجب استخدام الخطافات فقط في طرق البناء. الآن لديك قيود إضافية تقلق بشأنها.

من المحتمل أن يكون لدي أقل من 200 سطر من التعليمات البرمجية التي تتضمن نقاط التركيز البؤرية ، أو وحدات التحكم النصية ، أو موفري الفردي أو طرق دورة الحياة المختلفة لأدوات الحالة الخاصة بي ، في مشروع يحتوي على 15 ألف سطر من التعليمات البرمجية. ما هو الحمل المعرفي الزائد الذي تتحدث عنه؟

تضمين التغريدة
يمكننا أن نختلف دون قتال.


قيود الخطافات هي أقل ما يقلقني.

نحن لا نتحدث عن الخطافات على وجه التحديد ، ولكن عن المشكلة.
إذا كان علينا ذلك ، فيمكننا التوصل إلى حل مختلف.

ما يهم هو المشكلة التي نريد حلها.

علاوة على ذلك ، لا يمكن استخدام الأدوات الذكية إلا داخل الإنشاء وبدون قيد أو شرط (أو نقوم بتغيير عمق الشجرة بطريقة أخرى ، وهو أمر محظور)

هذا مطابق لقيود الخطافات ، لكنني لا أعتقد أن الناس اشتكوا من ذلك.

علاوة على ذلك ، لا يمكن استخدام الأدوات الذكية إلا داخل الإنشاء وبدون قيد أو شرط (أو نقوم بتغيير عمق الشجرة بطريقة أخرى ، وهو أمر محظور)

هذا مطابق لقيود الخطافات ، لكنني لا أعتقد أن الناس اشتكوا من ذلك.

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

إن إجباري على وضع منطق التهيئة من أي نوع في طريقة الإنشاء لا يشبه في أي مكان "إجباري" على تكوين شجرة عناصر واجهة مستخدم في طريقة الإنشاء. السبب الكامل لطريقة البناء هو اتخاذ حالة موجودة (مجموعة من المتغيرات) وإنتاج شجرة عنصر واجهة مستخدم يتم رسمها بعد ذلك.

على العكس من ذلك ، سأعارض أيضًا إجباري على إضافة رمز ينشئ عناصر واجهة مستخدم داخل طرق initState / didUpdateWidget.

كما هو الحال الآن ، فإن طرق دورة حياة العناصر ذات الحالة الخاصة لها دور واضح جدًا وتجعل من السهل جدًا فصل الكود بمخاوف مختلفة تمامًا.

من الناحية المفاهيمية ، بدأت أفهم المشكلات الموضحة هنا ، لكنني ما زلت أخفق في رؤيتها كمشكلة فعلية. ربما تساعدني بعض الأمثلة الحقيقية (التي ليست تطبيق العداد) في تغيير رأيي.

كملاحظة جانبية ، Riverpod ، تجربتي الأخيرة ، لديها بعض الأفكار الشبيهة للغاية ، دون قيود.

على سبيل المثال ، يحل:

Consumer(
  provider: provider,
  builder: (context, value) {
    return Consumer(
      provider: provider2,
      builder: (context, value2) {
        return Text('$value $value2');
      },
    );
  },
)

من خلال:

Consumer(
  builder (context, watch) {
    final value = watch(provider);
    final value2 = watch(provider2);
  },
)

حيث يمكن استدعاء watch بشروط:

Consumer(
  builder: (context, watch) {
    final value = watch(provider);
    if (something) {
      final value2 = watch(provider2);
    }
  },
)

يمكننا حتى التخلص من Consumer بالكامل من خلال الحصول على فئة أساسية مخصصة StatelessWidget / StatefulWidget :

class Example extends ConsumerStatelessWidget {
  <strong i="21">@override</strong>
  Widget build(ConsumerBuildContext context) {
    final value = context.watch(provider);
    final value2 = context.watch(provider2);
  }
}

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

لذلك ما زلنا بعيدين عن مرونة الخطافات

rousselGit أعتقد أنه بدون تمديد StatelessWidget / StatefulWidget وإنشاء شيء مثل ConsumerStatelessWidget ، من الممكن الحصول على شيء مثل context.watch باستخدام طرق الامتداد على BuildContext class ووجود المزود يوفر وظيفة الساعة مع InheritedWidgets.

هذا موضوع مختلف. لكن tl ؛ دكتور ، لا يمكننا الاعتماد على InheritedWidgets كحل لهذه المشكلة: https://github.com/flutter/flutter/issues/30062

لحل هذه المشكلة ، سيؤدي استخدام InheritedWidgets إلى حظرنا بسبب https://github.com/flutter/flutter/issues/12992 و https://github.com/flutter/flutter/pull/33213

من الناحية المفاهيمية ، بدأت أفهم المشكلات الموضحة هنا ، لكنني ما زلت أخفق في رؤيتها كمشكلة فعلية.

مقارنة Flutter بـ SwiftUI ، من الواضح بالنسبة لي أن هناك مشكلة حقيقية ، أو بالأحرى - الأشياء ليست كبيرة كما يمكن أن تكون.

قد يكون من الصعب رؤيته ، لأن Flutter وآخرون عملوا بجد حوله: لدينا أغلفة لكل حالة محددة: AnimatedBuilder و StreamBuilder و Consumer و AnimatedOpacity وما إلى ذلك ، تعمل StatefulWidget بشكل رائع لتنفيذ هذه المرافق الصغيرة القابلة لإعادة الاستخدام ، ولكنها مستوى منخفض للغاية للمكونات الخاصة بالمجال غير القابلة لإعادة الاستخدام ، حيث قد يكون لديك العديد من وحدات التحكم في النص أو الرسوم المتحركة أو أيًا كان ما يستدعي منطق الأعمال. الحل المعتاد هو إما أن تقضم الرصاصة وتكتب كل ذلك ، أو أن تصنع شجرة مبنية بعناية من مقدمي الخدمات والمستمعين. لا يوجد أي من النهجين يشبع.

إنه مثل rousselGit يقول ، في الأيام الخوالي (UIKit) اضطررنا إلى إدارة UIViews يدويًا (ما يعادل RenderObjects) ، وتذكر نسخ القيم من النموذج إلى العرض والعودة ، وحذف العروض غير المستخدمة ، وإعادة التدوير ، وما إلى ذلك. لم يكن هذا علمًا للصواريخ ، ولم ير الكثير من الناس هذه المشكلة القديمة ، لكنني أعتقد أن الجميع هنا سيوافقون على أن Flutter حسن الموقف بشكل واضح.
مع الحالة ، تكون المشكلة متشابهة جدًا: إنها عمل ممل وعرضة للخطأ ويمكن أتمتة.

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

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

أشعر بالارتباك عندما تقول إن بناء مكونات خاصة بالمجال غير القابل لإعادة الاستخدام تحتاج إلى عنصر واجهة مستخدم عالي المستوى. عادة ما يكون العكس تماما.

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

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

مع الحالة ، تكون المشكلة متشابهة جدًا: إنها عمل ممل وعرضة للخطأ ويمكن أتمتة.

مرة أخرى. تريد أتمتة __ "مكونات خاصة بالمجال غير قابلة لإعادة الاستخدام ، حيث قد يكون لديك العديد من وحدات التحكم في النص أو الرسوم المتحركة أو أي استدعاءات منطق الأعمال لـ" __ ؟!

إنه مثل rousselGit يقول ، في الأيام الخوالي (UIKit) اضطررنا إلى إدارة UIViews يدويًا (ما يعادل RenderObjects) ، وتذكر نسخ القيم من النموذج إلى العرض والعودة ، وحذف العروض غير المستخدمة ، وإعادة التدوير ، وما إلى ذلك. لم يكن هذا علمًا للصواريخ ، ولم ير الكثير من الناس هذه المشكلة القديمة ، لكنني أعتقد أن الجميع هنا سيوافقون على أن Flutter حسن الموقف بشكل واضح.

لقد قمت بتطوير iOS و android منذ 6 إلى 7 سنوات (في الوقت الذي تحول فيه Android إلى تصميم المواد) ولا أتذكر أن أيًا من طرق الإدارة وإعادة التدوير هذه تمثل مشكلة ولا يبدو Flutter أفضل أو أسوأ. لا أستطيع التحدث عن الشؤون الحالية ، لقد استقلت عندما تم إطلاق Swift و Kotlin.

النموذج المعياري الذي أجبرني على كتابته في StatefulWidgets هو حوالي 1٪ من قاعدة الشفرة الخاصة بي. هل هو عرضة للخطأ؟ كل سطر من التعليمات البرمجية هو مصدر محتمل للأخطاء ، لذا تأكد. هل هو مرهق؟ 200 سطر من الكود ouot من 15000؟ لا أعتقد ذلك حقًا ، لكن هذا مجرد رأيي. تحتوي وحدات التحكم في النص / الرسوم المتحركة في Flutter وعقد التركيز البؤرية على مشكلات يمكن تحسينها ، ولكن الإسهاب ليس مشكلة.

أنا فضولي حقًا لمعرفة ما يطوره الناس لدرجة أنهم يحتاجون إلى الكثير من النماذج المعيارية.

يبدو الاستماع إلى بعض التعليقات هنا وكأن إدارة 5 أسطر من التعليمات البرمجية بدلاً من 1 هو أكثر صعوبة بـ 5 مرات. ليست كذلك.

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

يبدو حقًا أن الاختلاف بين أولئك الذين لا يرون المشكلة هنا والذين لا يرونهم هو أن الأول قد استخدم تركيبات مثل hook من قبل ، كما هو الحال في React و Swift و Kotlin ، والأخيرة لم تفعل ذلك ، مثل العمل في Java خالص أو Android. أعتقد أن الطريقة الوحيدة للاقتناع ، من واقع خبرتي ، هي تجربة الخطافات ومعرفة ما إذا كان بإمكانك العودة إلى الطريقة القياسية. في كثير من الأحيان ، لا يستطيع الكثير من الناس ، مرة أخرى ، من خلال تجربتي. أنت تعرفه عندما تستخدمه.

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

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

لقد أهدرت أيامًا في محاولة تقديم الخدمة ، وحتى المزيد من الأيام في تجربة الكتلة ، لم أجد أيًا منهما كان حلاً جيدًا. إذا كان يعمل للكم، عظيم.

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

أيًا كان كود التخفيض المعياري الذي يتم إضافته إلى إطار العمل ، آمل أن تُترك الأداة Stateful / StatelessWidgets دون تغيير. ليس هناك الكثير الذي يمكنني إضافته إلى هذه المحادثة.

لنبدأ من جديد ، في عالم افتراضي حيث يمكننا تغيير Dart ، دون الحديث عن الخطافات.

المشكلة التي تمت مناقشتها هي:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

هذا الرمز غير قابل للقراءة.

يمكننا إصلاح مشكلة قابلية القراءة عن طريق إدخال كلمة رئيسية جديدة تعمل على تغيير بناء الجملة إلى:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

هذه الشفرة أكثر قابلية للقراءة ، ولا علاقة لها بالخطافات ، ولا تعاني من قيودها.
لا يتعلق اكتساب قابلية القراءة بعدد الأسطر ، بل يتعلق بالتنسيق والمسافات البادئة.


ولكن ماذا عن عندما لا يكون Builder هو أداة الجذر؟

مثال:

Widget build(context) {
  return Scaffold(
    body: StreamBuilder(
      stream: stream,
      builder: (context, value) {
        return Consumer<Value2>(
          builder: (context, value2, child) {
            return Text('${value.data} $value2');
          },
        );
      },
    ),
  );
}

كان باستطاعتنا ان:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword StreamBuilder(stream: stream);
      final value2 = keyword Consumer<Value2>();
      return Text('${value.data} $value2');
    }
  );
}

ولكن كيف يرتبط هذا بقضية إعادة الاستخدام؟

سبب ارتباط ذلك هو أن البناة هم تقنيًا طريقة لإعادة استخدام منطق الحالة. لكن مشكلتهم هي أنهم يجعلون الكود غير سهل القراءة إذا كنا نخطط لاستخدام العديد من البناة ، كما في هذا التعليق https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

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

لذلك على سبيل المثال ، useFilter المذكور في هذا التعليق يمكن أن يكون:

FilterBuilder(
  debounceDuration: const Duration(seconds: 2),
  builder: (context, filter) {
    return TextField(onChange: (value) => filter.value = value);
  }
)

والتي يمكننا استخدامها بعد ذلك مع الكلمة الرئيسية الجديدة:

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
    final userId = keyword Consumer(authProvider).userId;
    final chatId = keyword Consumer(selectedChatProvider);
    final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

ماذا عن "الاستخراج كوظيفة" التي تحدثنا عنها باستخدام الخطافات ، لإنشاء خطافات / بناة مخصصة؟

يمكننا أن نفعل الشيء نفسه مع هذه الكلمة الرئيسية ، من خلال استخراج مجموعة من البناة في دالة:

Builder<Chat> ChatBuilder()  {
  final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
  final userId = keyword Consumer(authProvider).userId;
  final chatId = keyword Consumer(selectedChatProvider);
  final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

  return Builder(chat);
}

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    final chat = keyword ChatBuilder();

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

من الواضح أنه لم يتم التفكير كثيرًا في جميع الآثار المترتبة على مثل هذا النحو. لكن هذه هي الفكرة الأساسية.


الخطافات هي هذه الميزة.
توجد قيود الخطافات لأنه يتم تنفيذها كحزمة بدلاً من ميزة لغة.

والكلمة الرئيسية هي use ، بحيث يصبح keyword StreamBuilder use StreamBuilder ، والذي يتم تنفيذه في النهاية كـ useStream

هذا الرمز أكثر قابلية للقراءة بشكل ملحوظ

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

ومع ذلك ، فإن الخطوة التالية هي العمل على تطبيق @ TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart)

لما يستحق ، https://github.com/flutter/flutter/issues/51752#issuecomment -670959424 يوازي إلى حد كبير إلهام الخطافات في React. يبدو نمط Builder مطابقًا لنمط Render Props الذي كان سائدًا في React (ولكنه أدى إلى أشجار عميقة مماثلة). في وقت لاحق ، اقترح trueadm بناء جملة sugar لـ Render Props ، وبعد ذلك أدى ذلك إلى Hooks (لإزالة وقت التشغيل

`Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}`

إذا كانت المقروئية والمسافة البادئة هي المشكلة ، فيمكن إعادة كتابتها كـ

  <strong i="8">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) => buildStreamBuilder(value),
    );
  }

  StreamBuilder<int> buildStreamBuilder(String value) => StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) => buildTweenAnimationBuilder(value, value2),
      );

  Widget buildTweenAnimationBuilder(String value, AsyncSnapshot<int> value2) =>
      TweenAnimationBuilder<double>(
        duration: Duration(milliseconds: 500),
        tween: Tween(),
        builder: (context, value3, _) => Text('$value $value2 $value3'),
      );

إذا لم تكن الوظائف هي الشيء الذي تريده ، أو كنت بحاجة إلى إعادة الاستخدام ، فاستخرجها كأدوات

class NewWidget extends StatelessWidget {
  var someValueListenable;

  var someStream;

  <strong i="12">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) {
        return MyStreamedWidget(value, someStream);
      },
    );
  }
}

class MyStreamedWidget extends StatelessWidget {
  const MyStreamedWidget(
    this.value,
    this.someStream, {
    Key key,
  }) : super(key: key);

  final String value;
  final Stream someStream;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: someStream,
      builder: (context, value2) => MyAnimatedWidget(value, value2),
    );
  }
}

class MyAnimatedWidget extends StatelessWidget {
  final String value;
  final AsyncSnapshot<int> value2;

  const MyAnimatedWidget(
    this.value,
    this.value2, {
    Key key,
  }) : super(key: key);

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween(),
      builder: (context, value3, _) {
        return Text('$value $value2 $value3');
      },
    );
  }
}

لا يوجد شيء في مثالك يبرر كلمة رئيسية أو ميزة جديدة.

أنا أعرف ما ستقوله. يجب تمرير متغير "القيمة" عبر جميع استدعاءات عناصر واجهة المستخدم / الوظائف ، ولكن هذا مجرد نتيجة لكيفية تصميم تطبيقك. أقوم بتفكيك الكود الخاص بي باستخدام كل من طرق "build" وعناصر واجهة مستخدم مخصصة ، اعتمادًا على حالة الاستخدام ، ولا يتعين عليّ تمرير نفس المتغير إلى سلسلة من ثلاث مكالمات.

يمكن إعادة استخدام الكود القابل لإعادة الاستخدام عندما يعتمد بأقل قدر ممكن على الآثار الجانبية الخارجية (مثل InheritedWidgets أو (شبه) الحالة العالمية).

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

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

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

إذا كانت مسألة رأي ، فأعتقد أنها غير متوازنة تمامًا في اتجاه واحد.

  1. كلاهما له سحر. لا أعرف بالضرورة ما الذي يفعله أي من هؤلاء البناة داخليًا. الإصدار غير السحري هو كتابة النموذج الفعلي داخل هؤلاء البناة. يعد استخدام مزيج SingleAnimationTIckerProvider سحرًا أيضًا لـ 95٪ من مطوري Flutter.
  2. يقوم أحدهما بتشويش أسماء المتغيرات المهمة جدًا والتي سيتم استخدامها لاحقًا في الشجرة ، وهي value1 و value2 ، والآخر يضعها في المقدمة وفي المنتصف أعلى البنية. هذا هو فوز تحليل واضح / صيانة.
  3. يحتوي أحدهما على 6 مستويات من الانعطاف قبل أن تبدأ شجرة عنصر واجهة المستخدم ، بينما يحتوي الآخر على 0
  4. واحد هو 5 خطوط عمودية والآخر 15
  5. أحدهما يظهر قطعة المحتوى الفعلية بشكل بارز (نص ()) والآخر يخفيها ، متداخلة ، أسفل الشجرة. فوز تحليل واضح آخر.

بشكل عام ، كان بإمكاني أن أرى أن هذا ربما يكون مسألة ذوق. لكن لدى Flutter على وجه التحديد مشكلة في عدد الخطوط والاستقراء والإشارة: نسبة

TL ؛ DR - أي شيء يقلل بشكل كبير من المسافة البادئة وعدد الخطوط في Flutter ، له قيمة مضاعفة ، نظرًا لارتفاع عدد الخطوط بشكل عام والمسافة البادئة المتأصلة في Flutter.

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

إلا إذا كان تغييرًا في إطار العمل الأساسي ، فهذا يؤثر علي ، أليس كذلك؟

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

حسنًا ، هذا الحصان قد تعرض للضرب حتى الموت مرات لا تحصى ، لذا لن أقع في فخ الرد على أي تعليقات أخرى.

البناة هم من الناحية الفنية طريقة لإعادة استخدام منطق الدولة. لكن مشكلتهم هي أنهم يجعلون الكود غير سهل القراءة.

هذا يوضح ذلك تمامًا. للتفكير في الأمر من منظور Flutter ، نحتاج إلى بناة من خط واحد.

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

شعار "كل شيء عبارة عن عنصر واجهة مستخدم" على وجه التحديد ليس شيئًا جيدًا هنا. عادةً ما تكون الشفرة ذات الصلة في أداة الإنشاء هي مجرد دعامات الإعداد ، والشيء ذي الحالة الذي تعيده والذي يحتاجه بناء fxn. كل فاصل سطر وعلامة تبويب لا طائل من ورائه.

إلا إذا كان تغييرًا في إطار العمل الأساسي ، فهذا يؤثر علي ، أليس كذلك؟

Rudiksz لا أعتقد أن أي شخص يقترح علينا تغيير الحاجيات ذات الحالة. يمكنك دائمًا استخدامها في شكلها الحالي إذا كنت تريد ذلك. أيًا كان الحل الذي قد نتوصل إليه ، فإنه إما أن يستخدم عناصر واجهة مستخدم ذات حالة بدون تغييرات ، أو نوع آخر من عناصر واجهة المستخدم بالكامل. نحن لا نقول أن الأدوات المصغرة ذات الحالة سيئة ، فقط لأننا نرغب في نوع آخر من الأدوات يسمح بمزيد من حالة عنصر واجهة المستخدم القابلة للإنشاء. فكر في الأمر على أنه عنصر واجهة مستخدم ذي حالة ، بدلاً من كائن حالة واحد مرتبط به يحتوي على كائنات حالة متعددة ، ووظيفة بناء واحدة منفصلة ولكن لها حق الوصول إلى كائنات الحالة هذه. بهذه الطريقة ، يمكنك إعادة استخدام أجزاء الحالة المشتركة (جنبًا إلى جنب مع منطق الحالة المرتبط بهذه الحالة) مع initState و dispose المنفذة بالفعل من أجلك. حالة أكثر نمطية بشكل أساسي ، يمكن تكوينها بطرق مختلفة في مواقف مختلفة. مرة أخرى ، هذا ليس اقتراحًا ملموسًا ، ولكنه ربما طريقة أخرى للتفكير فيه. ربما يمكن أن يتحول إلى حل يشبه أكثر من flutter ، لكنني لا أعرف.

ومع ذلك ، فإن الخطوة التالية هي العمل على تطبيق @ TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart)

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

إنه حقًا الفرق بين هذا:

<strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final controller = get AnimationController(vsync: this, duration: widget.duration);
    //do stuff
  }

وهذا:

  AnimationController _controller;

  <strong i="14">@override</strong>
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;
    }
  }

  <strong i="16">@override</strong>
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  <strong i="17">@override</strong>
  Widget build(BuildContext context) {
    //Do Stuff
  }

ببساطة لا توجد طريقة أفضل لتوضيح هذا. يمكنك تمديد حالة الاستخدام هذه إلى أي كائن من نوع وحدة التحكم. من المحتمل أن تكون AnimatorController و FocusController و TextEditingController هي الأكثر شيوعًا وإزعاجًا لإدارتها في الاستخدام اليومي. الآن فقط تصور 50 ، أو 100 من هؤلاء مبعدين في جميع أنحاء قاعدة الشفرة الخاصة بي.

  • لديك حوالي 1000-2000 سطر يمكن أن تختفي.
  • من المحتمل أن يكون لديك العشرات من الأخطاء و RTE (في مراحل مختلفة من التطوير) التي لم تكن بحاجة إلى الوجود أبدًا لأن بعض التجاوزات أو الأخرى مفقودة.
  • لديك أدوات ، والتي عند النظر إليها بأعين باردة ، يصعب التملص منها في لمحة. أحتاج إلى قراءة كل من هذه الإلغاءات ، لا يمكنني ببساطة أن أفترض أنها معيارية.

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

هذا صعب

نعم ، تصميم API صعب. مرحبا بكم في حياتي.

لذلك لا أعرف ما الذي ستظهره رؤية 30 من هذه الأمثلة ، ولكن 1 لا.

لن يساعدك 30 مثالًا ، إنه مثال واحد مفصل بدرجة كافية بحيث لا يمكن تبسيطه بطرق يمكنك أن تقول "حسنًا ، بالتأكيد ، هذا المثال يعمل ، لكنه لا يصلح لمثال حقيقي".

لن يساعدك 30 مثالًا ، إنه مثال واحد مفصل بما فيه الكفاية بحيث لا يمكن تبسيطه بطرق يمكنك قولها "حسنًا ، بالتأكيد ، هذا المثال يعمل ، لكنه ليس لمثال حقيقي".

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

إن طلب تطبيق يعرض شيئًا لا يمكن تبسيطه بشكل مختلف هو الحكم على سمكة من خلال قدرتها على تسلق الأشجار.

نحن لا نحاول الحكم على الخطافات فحسب ، بل نحاول تقييم الحلول المختلفة لمعرفة ما إذا كان هناك أي منها يلبي احتياجات الجميع.

(لدي فضول عن الطريقة التي ستقيم بها المقترحات المختلفة هنا ، إن لم يكن عن طريق كتابة الطلبات فعليًا في كل من المقترحات ومقارنتها. ما هو مقياس التقييم الذي تقترحه بدلاً من ذلك؟)

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

ما يجب أن نحكم عليه في الحل المقترح هو:

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

    • هل هي أكثر قابلية للقراءة؟

    • هل من الأسهل أن تكتب؟

  • كيف يمكن إعادة استخدام الكود؟
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟

    • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟

عند تقييمه على هذه الشبكة ، قد يكون عرض Property / addDispose جيدًا "هل الكود الناتج أفضل؟" يسجل ، لكن تقييمه سيئ مقابل كل من قابلية إعادة الاستخدام والمرونة.

لا أعرف كيف أجيب على هذه الأسئلة دون رؤية كل اقتراح قيد الاستخدام الفعلي.

لماذا ا؟

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

يمكننا أخذ أي * Builder موجود ومحاولة إعادة تنفيذه باستخدام الحل المقترح.
يمكننا أيضًا محاولة إعادة تنفيذ الخطافات المدرجة في هذا الخيط ، أو بعض الخطافات المصنوعة في مجتمع React (هناك العديد من مجموعات الخطافات المتوفرة عبر الإنترنت).

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

لسوء الحظ ، لا أشارك غرائزك هنا (كما يتضح من حقيقة أنني اعتقدت أن الخاصية (https://github.com/flutter/flutter/issues/51752#issuecomment-667737471) عملت بشكل رائع حتى قلت أنه كان عليها التعامل مع القيم من عنصر واجهة مستخدم وحالة محلية باستخدام نفس واجهة برمجة التطبيقات ، والتي لم أكن أدرك أنها كانت قيدًا حتى طرحتها). إذا قدمت نسخة من الملكية تحل هذه المشكلة أيضًا ، فهل ستكون بالتأكيد على ما يرام ، أم ستكون هناك مشكلة جديدة لا تغطيها؟ بدون هدف نتفق جميعًا على أنه الهدف ، لا أعرف ما الذي نصمم الحلول له.

يمكننا أخذ أي * Builder موجود ومحاولة إعادة تنفيذه باستخدام الحل المقترح.

من الواضح أنه لا _ أي _. على سبيل المثال ، لقد قدمت واحدًا في OP ، وعندما قدمت اقتراح الملكية الأول الخاص بي (https://github.com/flutter/flutter/issues/51752#issuecomment-664787791) أشرت إلى مشاكل معه لم يتم توضيحها بواسطة المنشئ الأصلي.

يمكننا أيضًا محاولة إعادة تنفيذ الخطافات المدرجة في هذا الخيط ، أو بعض الخطافات المصنوعة في مجتمع React (هناك العديد من مجموعات الخطافات المتوفرة عبر الإنترنت).

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

لن يساعدك 30 مثالًا ، إنه مثال واحد مفصل بدرجة كافية بحيث لا يمكن تبسيطه بطرق يمكنك أن تقول "حسنًا ، بالتأكيد ، هذا المثال يعمل ، لكنه لا يصلح لمثال حقيقي".

لن يكون هناك أبدًا أي شيء أكثر تفصيلاً من الرغبة البسيطة في استخدام AnimatorController (أو أي مكون آخر يمكن إعادة استخدامه يمكن أن تفكر فيه) بدون أداة إنشاء غير قابلة للقراءة أو مجموعة من نماذج دورة الحياة المعرضة للأخطاء.

لم يكن هناك حل مقترح يعالج مزايا المقروئية والمتانة المطلوبة ، بطريقة غرض عام.

أنا أصر على حقيقة أن _any_ builder سيفعل ذلك ، حيث يمكن إعادة تسمية هذه المشكلة إلى "نحن بحاجة إلى سكر تركيب للبناة" وتؤدي إلى نفس المناقشة.

يتم إجراء جميع الوسائط الأخرى (مثل إنشاء والتخلص من AnimationController ) على أساس أنه يمكن استخراجها أيضًا في المنشئ:

Widget build(context) {
  return AnimationControllerBuilder(
    duration: Duration(seconds: 2),
    builder: (context, animationController) {

    }
  );
}

في النهاية ، أعتقد أن المثال المثالي هو محاولة إعادة تطبيق StreamBuilder بالكامل واختباره في سيناريوهات مختلفة:

  • يأتي الدفق من Widget
  • // من InheritedWidget
  • من الدولة المحلية

واختبر كل حالة فردية مقابل "يمكن أن يتغير البث بمرور الوقت" ، لذلك:

  • didUpdateWidget مع دفق جديد
  • تم تحديث InheritedWidget
  • أطلقنا عليه اسم setState

هذا غير قابل للحل حاليًا مع Property أو onDispose

esDotDev هل يمكنك تعداد "مزايا سهولة القراءة والمتانة المطلوبة"؟ إذا قدم شخص ما اقتراحًا يتعامل مع AnimationController مع مزايا قابلية القراءة والمتانة ، فهل انتهينا من ذلك؟

rousselGit أنا لا

ولكن إذا ابتكر شخص ما حلًا يقوم بكل ما يفعله StreamBuilder ، ولكن بدون المسافة البادئة ، فهل يكون ذلك؟ ستكون سعيدا؟

على الأرجح نعم

سنحتاج بالطبع إلى مقارنة هذا الحل بالحلول الأخرى. لكن هذا سيصل إلى المستوى المقبول.

esDotDev هل يمكنك تعداد "مزايا سهولة القراءة والمتانة المطلوبة"؟

المتانة من حيث أنه يمكنها تغليف النموذج المعياري بالكامل حول التبعيات ودورة الحياة. بمعنى آخر. لا يجب أن أخبر fetchUser في كل مرة ، أنه من المحتمل أن يعيد البناء عند تغيير المعرف ، فهو يعرف أن يفعل ذلك داخليًا. يجب ألا تضطر إلى إخبار الرسوم المتحركة بالتخلص من نفسها في كل مرة يتم فيها التخلص من عنصر واجهة المستخدم الأصلي ، وما إلى ذلك (لا أفهم تمامًا ما إذا كان بإمكان الخاصية القيام بهذا الأمر). هذا يمنع المطورين من ارتكاب أخطاء في المهام عن ظهر قلب ، في جميع أنحاء قاعدة التعليمات البرمجية (إحدى الفوائد الرئيسية لاستخدام Builders imo حاليًا)

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

esDotDev إذا قدم شخص ما اقتراحًا يتعامل مع AnimationController مع مزايا قابلية القراءة والمتانة ،

إذا كنت تقصد بالتحديد AnimationController no. إذا كنت تقصد أي كائن يشبه AnimationController / FocusController / TextEdiitingController ، إذن نعم.

إن وجود واجهة برمجة تطبيقات تشبه الوظيفة تُرجع قيمة لها فترة حياة محددة غير واضحة

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

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

تضمين التغريدة

إلخ

يمكنك أن تكون أكثر تحديدا؟ (لهذا السبب اقترحت الخروج بتطبيق تجريبي يغطي جميع القواعد. ما زلت أعتقد أن هذه هي أفضل طريقة للقيام بذلك.) هل هناك ميزات مهمة بخلاف استدعاء المُهيئ فقط ما لم يتغير التكوين ويتخلص تلقائيًا الموارد المخصصة عند التخلص من العنصر المضيف؟

TextEdiitingController-like object

يمكنك أن تكون أكثر تحديدا؟ هل TextEditingController أكثر تفصيلاً من AnimationController بطريقة ما؟


تضمين التغريدة

ولكن إذا ابتكر شخص ما حلًا يقوم بكل ما يفعله StreamBuilder ، ولكن بدون المسافة البادئة ، فهل يكون ذلك؟ ستكون سعيدا؟

على الأرجح نعم

إليك الحل الذي يقوم بكل ما يقوم به StreamBuilder ، دون أي مسافات بادئة:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

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

إنها ببساطة نفس القيود التي يمتلكها البناة

يمكنك أن تكون أكثر تحديدا؟ هل TextEditingController أكثر تفصيلاً من AnimationController بطريقة ما؟

لا ، لكنها قد تقوم بأشياء مختلفة في init / dispose ، أو سترتبط بخصائص مختلفة ، وأود أن أغلف هذا النموذج المعياري المحدد.

esDotDev إذن هل تريد نفس الشيء مثل المنشئ ، ولكن بدون مسافة بادئة ، وعلى سطر واحد (باستثناء رد اتصال المنشئ نفسه ، على الأرجح)؟ المثال الذي نشرته للتو (https://github.com/flutter/flutter/issues/51752#issuecomment-671004483) هل هذا مع المنشئين اليوم ، لذا من المفترض أن هناك قيودًا إضافية تتجاوز ذلك؟

(FWIW ، لا أعتقد أن البناة ، أو شيء مثل البناة ، لكن على سطر واحد ، هم حل جيد ، لأنهم يتطلبون أن يكون لكل نوع بيانات مُنشئ خاص به ؛ لا توجد طريقة جيدة لبناء واحد على الطاير .)

(FWIW ، لا أعتقد أن البناة ، أو شيء مثل البناة ، لكن على سطر واحد ، هم حل جيد ، لأنهم يتطلبون أن يكون لكل نوع بيانات مُنشئ خاص به ؛ لا توجد طريقة جيدة لبناء واحد على الطاير .)

لا أفهم ما يعنيه هذا. هل يمكنك إعادة صياغته؟ 🙏

يجب عليك إنشاء AnimationBuilder للرسوم المتحركة و StreamBuilder لـ Streams وما إلى ذلك. بدلاً من مجرد الحصول على منشئ واحد والقول "إليك كيفية الحصول على واحد جديد ، وإليك كيفية التخلص منه ، وإليك كيفية إخراج البيانات" وما إلى ذلك عند إنشاء StatefulWidget.

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

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

أعتقد أن المطلوب هو شيء مثل (تحمل معي ، لا أفعل ، استخدم Streams كثيرًا):

Widget build(context) {
   var snapshot1 = get AsyncSnapshot<int>(stream1);
   var snapshot2 = get AsyncSnapshot<int>(stream2);
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

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

يجب عليك إنشاء AnimationBuilder للرسوم المتحركة و StreamBuilder لـ Streams وما إلى ذلك.

لا أرى في ذلك مشكلة. لدينا RestorableInt مقابل RestorableString مقابل RestorableDouble بالفعل

ويمكن للأدوية الجنيسة أن تحل ذلك:

GenericBuilder<Stream<int>>(
  create: (ref) {
    var controller = StreamController<int>();
    ref.onDispose(controller.close);
    return controller.stream;
  }
  builder: (context, Stream<int> stream) {

  }
)

وبالمثل ، يمكن أن يتضمن Flutter أو Dart واجهة Disposable إذا كانت هذه مشكلة بالفعل.

تضمين التغريدة

أعتقد أن المطلوب هو شيء مثل:

قد ينتهك ذلك بعض القيود المعقولة جدًا التي ذكرها الآخرون (على سبيل المثالRudiksz) ، أي ضمان عدم حدوث أي كود تهيئة على الإطلاق أثناء استدعاء التابع build.

تضمين التغريدة

لا أرى في ذلك مشكلة. لدينا RestorableInt مقابل RestorableString مقابل RestorableDouble بالفعل

ولدينا AnimationBuilder و StreamBuilder وما إلى ذلك ، نعم. في كلتا الحالتين إنه أمر مؤسف.

GenericBuilder

هذا مشابه لما اقترحته للممتلكات ، لكن إذا فهمت مخاوفك هناك ، فأنت تعتقد أن ذلك مطول للغاية.

في وقت سابق ، قلت إنه إذا قام شخص ما بإنشاء حل يقوم بكل ما يفعله StreamBuilder ، ولكن بدون المسافة البادئة ، فمن المحتمل أن تكون سعيدًا. أنت لم تعلق على محاولتي للقيام بذلك (https://github.com/flutter/flutter/issues/51752#issuecomment-671004483). هل أنت سعيد بهذا الحل؟

تضمين التغريدة

أعتقد أن المطلوب هو شيء مثل:

قد ينتهك ذلك بعض القيود المعقولة جدًا التي ذكرها الآخرون (على سبيل المثالRudiksz) ، أي ضمان عدم حدوث أي كود تهيئة على الإطلاق أثناء استدعاء التابع build.

ليس من المهم أن يكون هذا الرمز قيد الإنشاء. الجزء المهم هو ذلك

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

سيكون هذا رائعًا:

AsyncSnapshot<int> snapshot1 = createLifecycleState(widget.stream1);
AsyncSnapshot<int> snapshot2 = createLifecycleState(widget.stream2);
AniamtorController animator = createLifecycleState(duration: Duration(seconds: 1), (a)=>a.forward());

Widget build(context) {
   return Opacity(opacity: animator.value, child: Column(children: [Text(snapshot1.data), Text(snapshot2.data)]));
}

أو ، ليس مثل succint ، ولكن لا يزال أكثر قابلية للقراءة من استخدام المنشئين ، وأقل عرضًا للخطأ والخطأ من القيام بذلك مباشرة:

AsyncSnapshot<int> stream1;
AsyncSnapshot<int> stream2;
<strong i="18">@override</strong> 
void initState(){
    snapshot1 = createLifecycleState(widget.stream1);
    snapshot2 = createLifecycleState(widget.stream2);
   super.initState();
}

Widget build(context) {
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

لا أفهم لماذا نواصل العودة إلى الإسهاب.
لقد قلت صراحة عدة مرات أنها ليست هي المشكلة وأن المشكلة تكمن في قابلية إعادة الاستخدام مقابل سهولة القراءة مقابل المرونة.

حتى أنني صنعت شبكة لتقييم الحل https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 وحالة اختبار https://github.com/flutter/flutter/issues/51752#issuecomment - 671002248


في وقت سابق ، قلت إنه إذا قام شخص ما بإنشاء حل يقوم بكل ما يفعله StreamBuilder ، ولكن بدون المسافة البادئة ، فمن المحتمل أن تكون سعيدًا. أنت لم تعلق على محاولتي القيام بذلك (# 51752 (تعليق)). هل أنت سعيد بهذا الحل؟

أن يصل إلى الحد الأدنى المقبول من المرونة.

تقييمه حسب https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 يعطي:

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

      _ الصيغة الافتراضية (StreamBuilder دون القرصنة) أقل عرضة للخطأ. هذا الحل لا يتجنب الأخطاء ، بل يخلق بعض_ ❌

    • هل هي أكثر قابلية للقراءة؟

      _من الواضح أنه ليس أكثر قابلية للقراءة_ ❌

    • هل من الأسهل أن تكتب؟

      _ليس من السهل الكتابة_ ❌

  • كيف يمكن إعادة استخدام الكود؟
    _StreamBuilder ليس مرتبطًا بدورات القطعة / الدولة / الحياة ، لذلك هذا التمرير_ ✅
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟
    _يمكننا صنع بناة مخصصين ، واستخدام هذا النمط. إذن هذا يمر_. ✅
  • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟
    _لا ، الصيغة متسقة نسبيًا_. ✅
  1. يمكن أن تمتد هذه الميزة IMO إلى جميع أدوات البناء ، بما في ذلك LayoutBuilder على سبيل المثال.
  2. يجب أن تكون هناك طريقة لتعطيل الاستماع ، بحيث يمكنك إنشاء وحدات تحكم 10x وتمريرها إلى أوراق الشجر لإعادة البناء ، أو يحتاج الرفرفة إلى معرفة أي جزء من الشجرة يستخدم القيمة التي حصل عليها المنشئ بطريقة ما.
  3. لا ينبغي أن يكون استخدام هذا أكثر إطالة من الخطافات.
  4. يجب تمديد المترجم للتعامل مع هذا بشكل صحيح.
  5. هناك حاجة إلى مساعدين التصحيح. لنفترض أنك وضعت Breakpoins في إحدى أدواتك التي تستخدم هذا. عند الوصول إلى نقطة توقف داخل طريقة الإنشاء ، نظرًا لأنه تم تشغيل أحد البناة ، يمكن لـ IDE عرض معلومات إضافية لكل منشئ تم استخدامه:
Widget build(context) {
   // this builder is not highlighted, but you can hover over it to see how often it rebuilds, how heavy were those rebuilds, and when was the last rebuild
   var snapshot1 = keyword StreamBuilder(stream1);
   // this builder will be highlighted because it triggered the rebuild
   var constraints = keyword LayoutBuilder(); 

   // <-- I had a breakpoint here and parent constraint changed, breakpoints got reached.
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

أيضا ، Hixie

قد ينتهك ذلك بعض القيود المعقولة جدًا التي ذكرها الآخرون (على سبيل المثالRudiksz) ، أي ضمان عدم حدوث أي كود تهيئة على الإطلاق أثناء استدعاء التابع build.

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

esDotDev يبدو ما تصفه مشابهًا جدًا لما اقترحته سابقًا مع الخاصية (انظر على سبيل المثال https://github.com/flutter/flutter/issues/51752#issuecomment-664787791 أو https://github.com/flutter/flutter/ القضايا / 51752 # issuecomment-667737471). هل هناك شيء يمنع إنشاء هذا النوع من الحلول كحزمة واحدة؟ بمعنى ، ما التغيير الذي سيحتاجه إطار العمل الأساسي للسماح لك باستخدام هذا النوع من الميزات؟

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

المشكلة التي أواجهها مع شبكتك هي أنه إذا قمت بتطبيقها على الحلول حتى الآن ، فسأحصل على هذا ، والذي أفترض أنه مختلف تمامًا عما ستحصل عليه:

StatefulWidget

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

      _انها نفس البنية الافتراضية ، وهي ليست عرضة للخطأ بشكل خاص. _ 🔷

    • هل هي أكثر قابلية للقراءة؟

      _ إنه نفس الشيء ، لذا فهو مقروء بشكل متساوٍ ، ويمكن قراءته بشكل معقول ._ 🔷

    • هل من الأسهل أن تكتب؟

      _ الأمر نفسه ، لذا من السهل أيضًا الكتابة ، وهو أمر سهل إلى حد ما ._ 🔷

  • كيف يمكن إعادة استخدام الكود؟
    _هناك القليل جدًا من التعليمات البرمجية لإعادة الاستخدام. _ ✅
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟
    _هذا هو الأساس ._ 🔷
  • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟
    _لا يبدو هكذا. _ ✅

الاختلافات في الممتلكات

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

      _إنه ينقل الكود إلى مكان مختلف ، لكنه لا يقلل بشكل خاص من عدد الأخطاء ._ 🔷

    • هل هي أكثر قابلية للقراءة؟

      _إنه يضع كود التهيئة وكود التنظيف وكود دورة الحياة الأخرى في نفس المكان ، لذا فهو أقل وضوحًا ._ ❌

    • هل من الأسهل أن تكتب؟

      _يجمع بين كود التهيئة ورمز التنظيف وكود دورة الحياة الأخرى ، لذلك من الصعب الكتابة ._ ❌

  • كيف يمكن إعادة استخدام الكود؟
    _إمكانية إعادة استخدامها تمامًا مثل StatefulWidget ، فقط في أماكن مختلفة ._ ✅
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟
    _ هذا هو السكر النحوي لـ StatefulWidget ، لذلك لا فرق. _ 🔷
  • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟
    _ الأداء واستخدام الذاكرة سيتأثران قليلاً ._ ❌

الاختلافات في بناة

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

      _It هو في الأساس حل StatefulWidget ولكن تم أخذها في الاعتبار ؛ يجب أن تكون الأخطاء متكافئة. _ 🔷

    • هل هي أكثر قابلية للقراءة؟

      _تعد طرق الإنشاء أكثر تعقيدًا ، وينتقل باقي المنطق إلى عنصر واجهة مستخدم مختلف ، لذلك نفس الشيء تقريبًا ._ 🔷

    • هل من الأسهل أن تكتب؟

      _أصعب من الكتابة في المرة الأولى (إنشاء عنصر واجهة المستخدم) ، يكون أسهل قليلاً بعد ذلك ، لذلك تقريبًا نفس الشيء. _ 🔷

  • كيف يمكن إعادة استخدام الكود؟
    _إمكانية إعادة استخدامها تمامًا مثل StatefulWidget ، فقط في أماكن مختلفة ._ ✅
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟
    _ هذا هو السكر النحوي لـ StatefulWidget ، لذلك لا فرق في الغالب. من الأفضل في بعض الجوانب ، على سبيل المثال ، تقليل مقدار التعليمات البرمجية التي يجب تشغيلها عند التعامل مع تغيير التبعية ._ ✅
  • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟
    _لا يبدو هكذا. _ ✅

حلول تشبه الخطاف

  • هل الكود الناتج أفضل من الناحية الموضوعية من الصيغة الافتراضية؟

    • هل تتجنب الأخطاء؟

      _ يشجع على الأنماط السيئة (مثل البناء في طريقة البناء) ، ويخاطر بالأخطاء إذا تم استخدامه عن طريق الخطأ مع الشروط ._

    • هل هي أكثر قابلية للقراءة؟

      _زيادة عدد المفاهيم التي يجب أن تكون معروفة لفهم طريقة البناء. _ ❌

    • هل من الأسهل أن تكتب؟

      _المطور يجب أن يتعلم كيفية كتابة الخطافات ، وهو مفهوم جديد ، أصعب جدًا ._ ❌

  • كيف يمكن إعادة استخدام الكود؟
    _إمكانية إعادة استخدامها تمامًا مثل StatefulWidget ، فقط في أماكن مختلفة ._ ✅
  • كم عدد المشكلات التي يمكن حلها باستخدام واجهة برمجة التطبيقات هذه؟
    _ هذا هو السكر النحوي لـ StatefulWidget ، لذلك لا فرق. _ 🔷
  • هل نفقد بعض الفوائد لبعض المشاكل المحددة؟
    _ الأداء واستخدام الذاكرة يعانيان. _ ❌

لا أفهم لماذا نواصل العودة إلى الإسهاب.
لقد قلت صراحة عدة مرات أنها ليست هي المشكلة وأن المشكلة تكمن في قابلية إعادة الاستخدام مقابل سهولة القراءة مقابل المرونة.

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

تضمين التغريدة

  1. يمكن أن تمتد هذه الميزة IMO إلى جميع أدوات البناء ، بما في ذلك LayoutBuilder على سبيل المثال.

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

  1. يجب أن تكون هناك طريقة لتعطيل الاستماع ، بحيث يمكنك إنشاء وحدات تحكم 10x وتمريرها إلى أوراق الشجر لإعادة البناء ، أو يحتاج الرفرفة إلى معرفة أي جزء من الشجرة يستخدم القيمة التي حصل عليها المنشئ بطريقة ما.

لست متأكدًا تمامًا مما يعنيه هذا. بقدر ما أستطيع أن أقول ، يمكنك القيام بذلك اليوم مع المستمعين والبناة (على سبيل المثال ، أستخدم ValueListenableBuilder في التطبيق الذي ذكرته سابقًا للقيام بذلك بالضبط).

قد ينتهك ذلك بعض القيود المعقولة جدًا التي ذكرها الآخرون (على سبيل المثالRudiksz) ، أي ضمان عدم حدوث أي كود تهيئة على الإطلاق أثناء استدعاء التابع build.

نحن نفعل ذلك بالفعل ضمنيًا باستخدام * بناة.

لا أعتقد أن هذا دقيق. يعتمد ذلك على المنشئ ، لكن البعض يعمل بجد لفصل initState ، و didChangeDependencies ، و didUpdateWidget ، وبناء منطق ، بحيث لا يحتاج سوى الحد الأدنى من التعليمات البرمجية لتشغيل كل بناء بناءً على ما تغير. على سبيل المثال ، يسجل ValueListenableBuilder المستمعين فقط عند إنشائه لأول مرة ، ويمكن لمنشئه إعادة التشغيل دون إعادة تشغيل العنصر الرئيسي أو الحالة المبدئية للمنشئ. هذا ليس شيئًا يمكن أن يفعله هوكس.

esDotDev يبدو ما تصفه مشابهًا جدًا لما اقترحته سابقًا مع الخاصية (انظر على سبيل المثال # 51752 (تعليق) أو # 51752 (تعليق) ).

إذا فهمت بشكل صحيح ، يمكننا أن نجعل UserProperty يتعامل تلقائيًا مع DidDependancyChange لـ UserId ، أو AnimationProperty ، أو أي خاصية أخرى نحتاجها للتعامل مع init / update / dispose لهذا النوع؟ ثم يبدو هذا لطيفا بالنسبة لي. يمكن إنشاء حالات الاستخدام الأكثر شيوعًا بسرعة.

الشيء الوحيد الذي طردني هو بناء المستقبل هنا. لكن أعتقد أن هذا يرجع فقط إلى المثال الذي اخترته؟

على سبيل المثال ، هل يمكنني إنشاء هذا؟

class _ExampleState extends State<Example> with PropertyManager {
  AnimationProperty animProp1;
  AnimationProperty animProp2;

  <strong i="15">@override</strong>
  void initProperties() {
    super.initProperties();
    anim1= register(anim1, AnimationProperty (
      AnimationController(duration: Duration(seconds: 1)),
      initState: (animator) => animator.forward()
      // Not dealing with updates or dispose here, cause AnimationProperty handles it
    ));
   anim2 = register(anim2, AnimationProperty(
       AnimationController(duration: Duration(seconds: 2))..forward(),
   ));
  }

  <strong i="16">@override</strong>
  Widget build(BuildContext context) {
    return Column(children: [
       FadeTransition(opacity: anim1, child: ...),
       FadeTransition(opacity: anim2, child: ...),
   ])
  }
}

إذا كان الأمر كذلك ، فهذا هو LGTM تمامًا! من حيث الإضافة إلى إطار العمل ، إنها حالة ما إذا كان يجب ترقية هذا إلى نهج نحوي من الدرجة الأولى (مما يعني أنه يصبح ممارسة عامة في غضون عام أو نحو ذلك) ، أو ما إذا كان موجودًا كمكوِّن إضافي يحتوي على نسبة مئوية من رقم واحد للمطورين استعمال.

إنها مسألة ما إذا كنت تريد أن تكون قادرًا على تحديث الأمثلة المطولة والمعرضة للخطأ (قليلاً؟) مع بناء جملة أكثر إيجازًا. الاضطرار إلى مزامنة الخصائص يدويًا والتخلص يدويًا من الأشياء () يؤدي إلى أخطاء وتحميل معرفي.

سيكون من الرائع لو استطاع المطور استخدام الرسوم المتحركة ، مع القيام بالتحديث الصحيح والتخلص من الخصائص وتصحيحها ، دون الحاجة إلى التفكير مرتين في الأمر (تمامًا كما نفعل عندما نستخدم TweenAnimationBuilder الآن ، وهذا هو السبب الرئيسي الذي نوصي به يقوم جميع مطورينا افتراضيًا باستخدامه بدلاً من إدارة الرسوم المتحركة يدويًا).

إذا كان الأمر كذلك ، فهذا هو LGTM تمامًا! من حيث الإضافة إلى إطار العمل ، إنها حالة ما إذا كان يجب ترقية هذا إلى نهج نحوي من الدرجة الأولى (مما يعني أنه يصبح ممارسة عامة في غضون عام أو نحو ذلك) ، أو ما إذا كان موجودًا كمكوِّن إضافي يحتوي على نسبة مئوية من رقم واحد للمطورين استعمال.

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

الشيء الوحيد الذي طردني هو بناء المستقبل هنا. لكن أعتقد أن هذا يرجع فقط إلى المثال الذي اخترته؟

كنت أحاول تناول مثال أعطى rousselGit . من حيث المبدأ يمكن تكييفه للعمل من أجل أي شيء.

على سبيل المثال ، هل يمكنني إنشاء هذا؟

قد ترغب في نقل مُنشئ AnimationController إلى الإغلاق الذي سيتم استدعاؤه ، بدلاً من الاتصال به في كل مرة ، حيث يتم استدعاء initProperties أثناء إعادة التحميل السريع للحصول على الإغلاق الجديد ولكنك عادةً لا تريد إنشاء وحدة تحكم جديدة أثناء إعادة التحميل الساخن (على سبيل المثال ، ستعيد تعيين الرسوم المتحركة). لكن نعم ، بخلاف ذلك يبدو جيدًا. يمكنك حتى إنشاء AnimationControllerProperty يأخذ وسيطات المُنشئ AnimationController ويفعل الشيء الصحيح معهم (على سبيل المثال ، تحديث المدة عند إعادة التحميل السريع إذا تغيرت).

سيكون من الرائع لو استطاع المطور استخدام الرسوم المتحركة ، مع القيام بالتحديث الصحيح والتخلص من الخصائص وتصحيحها ، دون الحاجة إلى التفكير مرتين في الأمر (تمامًا كما نفعل عندما نستخدم TweenAnimationBuilder الآن ، وهذا هو السبب الرئيسي الذي نوصي به يقوم جميع مطورينا افتراضيًا باستخدامه بدلاً من إدارة الرسوم المتحركة يدويًا).

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

يمكنك حتى إنشاء AnimationControllerProperty يأخذ وسيطات مُنشئ AnimationController ويفعل الشيء الصحيح معهم (على سبيل المثال ، تحديث المدة عند إعادة التحميل السريع إذا تغيرت).

شكرًا @ Hixie ، هذا رائع حقًا وأعتقد أنه يعالج المشكلة جيدًا.

لا أقترح على المطورين أن يفكروا أبدًا في هذه الأشياء ، لكنني أعتقد أن حالة استخدام هذه الأشياء بنسبة 99٪ تقريبًا مرتبطة دائمًا بـ StatefulWidget التي يتم استخدامها فيها ، وفعل أي شيء آخر غير ذلك يؤدي بالفعل إلى وصولك إلى أرض مطورة وسيطة.

مرة أخرى ، لا أرى كيف يختلف هذا اختلافًا جوهريًا عن التوصية بـ TweenAnimationBuilder على AnimatorController الخام. إنها في الأساس فكرة أنه إذا كنت تريد احتواء / إدارة الدولة بالكامل داخل هذه الحالة الأخرى (وهذا هو ما تريده عادةً) ، فافعل ذلك بهذه الطريقة بشكل أبسط وأكثر قوة.

في هذه المرحلة ، يجب علينا تنظيم مكالمة ومناقشتها مع مختلف الأطراف المعنية.
لأن هذه المناقشة لا تتقدم ، لأننا نجيب على نفس السؤال مرارًا وتكرارًا.

لا أفهم كيف بعد مثل هذه المناقشة الطويلة ، مع تقديم العديد من الحجج ، لا يزال بإمكاننا القول بأن البناة لا يتجنبون الأخطاء مقارنة بـ StatefulWidget ، أو أن الخطافات ليست أكثر قابلية لإعادة الاستخدام من StatefulWidgets.

هذا أمر محبط بشكل خاص للجدل بالنظر إلى أن جميع الأطر التعريفية الرئيسية (React و Vue و Swift UI و Jetpack Compose) لديها طريقة أو بأخرى لحل هذه المشكلة محليًا.
يبدو أن Flutter هو الوحيد الذي يرفض النظر في هذه المشكلة.

esDotDev السبب الرئيسي لاستخدام IMHO في AnimationBuilder أو TweenAnimationBuilder أو ValueListenableBuilder هو أنهم يعيدون البناء فقط عندما تتغير القيمة ، دون إعادة بناء بقية عنصر واجهة المستخدم المضيف . إنه شيء يتعلق بالأداء. لا يتعلق الأمر حقًا بالإسهاب أو إعادة استخدام الكود. أعني أنه من الجيد استخدامها لهذه الأسباب أيضًا ، إذا وجدتها مفيدة لتلك الأسباب ، لكن هذه ليست حالة الاستخدام الرئيسية ، على الأقل بالنسبة لي. إنه أيضًا شيء لا تمنحك إياه الملكية (أو الخطافات). مع هؤلاء ، ينتهي بك الأمر إلى إعادة بناء عنصر واجهة المستخدم _entire_ عندما يتغير شيء ما ، وهذا ليس جيدًا للأداء.

تضمين التغريدة

يبدو أن Flutter هو الوحيد الذي يرفض النظر في هذه المشكلة.

لقد قضيت حرفيًا ساعات من وقتي في نهاية هذا الأسبوع ، ناهيك عن ساعات عديدة من وقت Google قبل ذلك ، مع الأخذ في الاعتبار هذه المشكلة ، ووصف الحلول الممكنة ، ومحاولة استخلاص ما نحاول حله بالضبط. من فضلك لا تخلط بين عدم الفهم حول المشكلة مع رفض النظر فيها. خاصةً عندما وصفت بالفعل أفضل شيء يمكن فعله للمضي قدمًا (إنشاء تطبيق تجريبي يحتوي على كل منطق الحالة "المطول جدًا أو الصعب جدًا" ، لاقتباس عنوان المشكلة ، وإعادة استخدامه) ، والذي الأشخاص الآخرون في هذا الخطأ قد أخذوا على عاتقهم مهمة ورفضت المشاركة فيها.

The main reason IMHO to use an AnimationBuilder or TweenAnimationBuilder or ValueListenableBuilder is that they rebuild only when the value changes, without rebuilding the rest of their host widget. It's a performance thing.

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

من واقع خبرتنا ، فإن تكلفة إعادة طلاء وحدات البكسل ، والتي يبدو أنها تحدث للشجرة الكاملة ما لم تكن عازمًا على تحديد RepaintBoundaries ، هي عامل أكثر أهمية في أداء العالم الحقيقي من تكاليف تخطيط عنصر واجهة المستخدم الجزئية. خاصة عندما تدخل في نطاق شاشة 4K.

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

في كثير من الأحيان لا نقوم بذلك ، وفي هذه الحالة ، يقوم Builder فقط بإضافة الفوضى ، لكننا نقبله ، لأن البديل هو مجرد نوع مختلف من الفوضى ، وعلى الأقل مع Builder ، تكون الأشياء مضمونة بشكل أو بآخر خالية من الأخطاء. في الحالات التي يتم فيها إعادة بناء العرض بالكامل ، أو لا توجد بالضرورة عمليات إعادة بناء للعرض على الإطلاق (TextEditingController ، FocusController) باستخدام أداة إنشاء لا معنى له ، وفي جميع الحالات ، لا يكون لف النموذج اليدوي يدويًا في الأساس جافًا.

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

لا ، ولكن الأمر أشبه بمطالبة المجتمع ببناء TweenAnimationBuilder و ValueListenableBuilder وعدم تزويدهم بأداة StatefulWidget للبناء عليها.

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

StatefulWidget عبارة عن _lot_ من التعليمات البرمجية ، مقارنةً بالخاصية ، وهي ليست تافهة (على عكس الخاصية ، والتي تكون في الغالب رمزًا لاصقًا). ومع ذلك ، إذا كان من المنطقي إعادة استخدام Property على نطاق واسع (بدلاً من صياغة إصدارات مخصصة لكل تطبيق أو فريق بناءً على احتياجاته الدقيقة) ، فأنا أشجع شخصًا يدافع عن استخدامها لإنشاء حزمة وتحميلها إلى pub . الأمر نفسه ينطبق بالفعل على الخطافات. إذا كان هذا شيئًا يحبه المجتمع ، فسيتم استخدامه كثيرًا ، تمامًا مثل المزود. ليس من الواضح لماذا نحتاج إلى وضع شيء من هذا القبيل في الإطار نفسه.

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

ملاحظة حول "أولئك الذين يفضلون هذا الأسلوب". إذا كان بإمكانك حفظ 3 عمليات تجاوز و 15 إلى 30 سطرًا ، فسيكون ذلك بمثابة فوز للقراءة في معظم الحالات. موضوعيا imo يتحدث. كما أنه يزيل بشكل موضوعي فئتين كاملتين من الأخطاء (نسيان التخلص من الأشياء ، ونسيان تحديث الأقسام).

شكرًا جزيلاً على المناقشة الرائعة ، متحمس لرؤية أين يذهب هذا ، سأغادره هنا بالتأكيد :)

يؤسفني أن أقول إن هذا الموضوع يجعلني أشعر بخيبة أمل من العودة إلى الرفرفة التي كانت هي الخطة عند الانتهاء من مشروع آخر أعمل عليه. كما أنني أشعر بالإحباط بسبب

هذا أمر محبط بشكل خاص للجدل بالنظر إلى أن جميع الأطر التعريفية الرئيسية (React و Vue و Swift UI و Jetpack Compose) لديها طريقة أو بأخرى لحل هذه المشكلة محليًا.

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

أشعر (على الأقل بالنسبة لي) أن هذا لم يؤخذ على محمل الجد ، أنا آسف @ Hixie ، أعني أنه لم يؤخذ على محمل الجد بمعنى: We understand the problem and want to solve it . مثل الأطر التصريحية الأخرى على ما يبدو قد فعلت.
من ناحية أخرى ، لكن من نفس النوع ، لاحظ أشياء مثل هذه:

هل من الأسهل أن تكتب؟
يجب أن يتعلم المطور كيفية كتابة الخطافات ، وهو مفهوم جديد ، أصعب

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

هل من الأسهل أن تكتب؟
إنه نفس الشيء ، لذلك من السهل أيضًا الكتابة ، وهو أمر سهل إلى حد معقول

لا أعتقد أن ما تعنيه rousselGit مع is it easier to write? (سؤال إجابة منطقي) هو أنه إذا كانت الإجابة هي نفسها ، فلن تكون الإجابة false / undefined .

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

إنه بالتأكيد شيء نشأه الناس. إنه ليس شيئًا لدي تجربة عميقة معه. إنه ليس شيئًا شعرت أنه يمثل مشكلة عند كتابة تطبيقاتي الخاصة باستخدام Flutter. هذا لا يعني أنها ليست مشكلة حقيقية لبعض الناس.

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

آمل حقًا أن تأخذ roussel على عرضه وتنظم مكالمة ، ربما سيكون من الأسهل إجراء مناقشة في الوقت الفعلي حول هذا الأمر بدلاً من كتابة الأشياء ذهابًا وإيابًا طوال الوقت. إذا كان أي أشخاص من الأطر الأخرى قد حلوا المشكلة الموضحة في OP ، إذا كان أحدهم لطيفًا حقًا ، فربما يمكنهم المشاركة في المكالمة لفترة أيضًا ومشاركة 5 سنتات حول الأشياء التي تظهر. يمكن للمرء أن يسأل دائما.

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

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

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

Hixie نعم ، نحن بالتأكيد بحاجة إلى بعض العينات. سأقوم بإعداد شيء ما (لكنني ما زلت أعتقد أن هناك حاجة لتغييرات المترجم ، لذا قد تكون العينة غير مكتملة). الفكرة العامة هي - سكر تركيبي يعمل على تسطيح البناة ولا يهتم بكيفية تنفيذ المنشئ.

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

@ ايمانويل لوندمان

أشعر (على الأقل بالنسبة لي) أن هذا لم يؤخذ على محمل الجد ، أنا آسف @ Hixie ، أعني أنه لم يؤخذ على محمل الجد بمعنى: We understand the problem and want to solve it . مثل الأطر التصريحية الأخرى على ما يبدو قد فعلت.

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

تضمين التغريدة

ومع ذلك ، لدي انطباع أكثر من أي شخص في فريق Flutter ألقى نظرة أعمق على SwiftUI ، أعتقد أنه سيكون من السهل فهم مخاوفنا بطريقة أخرى.

لقد درست Swift UI. من المؤكد أن كتابة كود Swift UI أقل من كود Flutter ، لكن تكلفة القراءة عالية جدًا IMHO. هناك الكثير من "السحر" (بمعنى ، المنطق الذي يعمل بطرق غير واضحة في كود المستهلك). أستطيع أن أصدق تمامًا أنه أسلوب يفضله بعض الناس ، لكنني أعتقد أيضًا أن إحدى نقاط القوة في Flutter هي أنه يحتوي على القليل جدًا من السحر. هذا يعني أنك تكتب المزيد من التعليمات البرمجية أحيانًا ، ولكن هذا يعني أيضًا أن تصحيح أخطاء هذا الرمز أسهل بكثير.

أعتقد أن هناك متسعًا للعديد من أنماط الأطر. أسلوب MVC ، نمط التفاعل ، سحري مقتضب للغاية ، خالي من السحر ولكنه مطول ... إحدى مزايا بنية Flutter هي أن جانب قابلية النقل منفصل تمامًا عن الإطار نفسه ، لذلك من الممكن الاستفادة من جميع أدواتنا - دعم عبر النظام الأساسي ، وإعادة التحميل السريع ، وما إلى ذلك - ولكن قم بإنشاء إطار عمل جديد تمامًا. (هناك أطر Flutter أخرى ، مثل flutter_sprites ، بالفعل.) وبالمثل ، تم تصميم الإطار نفسه بطريقة الطبقات بحيث يمكنك على سبيل المثال إعادة استخدام كل منطق RenderObject الخاص بنا ولكن مع استبدال طبقة Widgets ، لذلك إذا كان الإسهاب في الحاجيات كثيرة جدًا ، يمكن لأي شخص إنشاء إطار عمل بديل يحل محله. وبالطبع هناك نظام التعبئة والتغليف بحيث يمكن إضافة الميزات دون فقدان أي من كود الإطار الحالي.

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


أحث الأشخاص المهتمين بهذه المشكلة على العمل مع TimWhiting لإنشاء تطبيق يوضح سبب رغبتك في إعادة استخدام الكود وما يبدو عليه اليوم عندما لا يمكنك ذلك (https://github.com/TimWhiting/local_widget_state_approaches). سيساعدنا هذا بشكل مباشر في إنشاء مقترحات حول كيفية معالجة هذه المشكلة بطريقة تلبي احتياجات _ جميع _ الأشخاص الذين يقومون بالتعليق هنا (بما في ذلك أولئك الذين يحبون الخطافات وأولئك الذين لا يحبون الخطافات).

لا يمكن أن يكون من الصعب حقًا فهم سبب رغبة المطورين في "_تركيب السكر الذي يسطح البناة ولا يهتم بكيفية تنفيذ المنشئ. _" كميزة من الدرجة الأولى. لقد حددنا المشكلات مع الأساليب البديلة مرارًا وتكرارًا.

باختصار ، يحل المنشئون مشكلة إعادة الاستخدام ، لكن يصعب قراءتها وتأليفها. تكمن "المشكلة" ببساطة في أننا نرغب في وظيفة شبيهة بوظيفة الباني التي يسهل قراءتها بشكل ملحوظ.

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

لقد أمضيت حرفيًا ساعات من وقتي في نهاية هذا الأسبوع ، ناهيك عن ساعات عديدة من وقت Google قبل ذلك ، مع الأخذ في الاعتبار هذه المشكلة ، ووصف الحلول الممكنة ، ومحاولة استخلاص ما نحاول حله بالضبط

أنا ممتن لذلك

من فضلك لا تخلط بين عدم الفهم حول المشكلة مع رفض النظر فيها

أنا بخير مع عدم فهمي ، لكن الوضع الحالي يبدو ميؤوسًا منه.
ما زلنا نتناقش حول النقاط التي تم طرحها في بداية المناقشة.

من وجهة نظري ، أشعر أنني قضيت ساعات في كتابة تعليقات مفصلة تعرض المشكلات المختلفة والإجابة على الأسئلة ، لكن تعليقاتي تم رفضها وطرح السؤال نفسه مرة أخرى.

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

تحتوي هذه التحليلات على عدد كبير من 👍 ويبدو أن الشعوب الأخرى تتفق معها

ومع ذلك ، وفقًا لإجابتك الأخيرة ، لا توجد مشكلة في سهولة القراءة: https://github.com/flutter/flutter/issues/51752#issuecomment -671009593

لقد اقترحت أيضًا:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

مع العلم أن هذا غير مقروء

من هذين التعليقين ، يمكننا أن نستنتج ما يلي:

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

هذا أمر محبط للاستماع ، مع الأخذ في الاعتبار أن الغرض الوحيد من الخطافات هو تحسين بناء جملة البناة - الذين هم في ذروة إعادة الاستخدام ولكن لديهم قابلية قراءة / كتابة ضعيفة
إذا لم نتفق على هذه الحقيقة الأساسية ، فأنا لست متأكدًا مما يمكننا فعله.

Hixie شكرًا ، فهذا يساعد كثيرًا في فهم وجهة نظرك. أوافق على أنهم ربما ذهبوا كثيرًا في سحر الشفرة ، لكنني متأكد من أن هناك على الأقل القليل من الأشياء التي حصلوا عليها بشكل صحيح.

وأنا أحب إلى حد كبير الهندسة المعمارية ذات الطبقات لـ Flutter. أود أيضًا الاستمرار في استخدام الأدوات. لذلك ربما يكون الجواب هو تحسين قابلية توسيع Dart & Flutter ، والتي ستكون بالنسبة لي:

اجعل إنشاء الكود أكثر سلاسة - قد يكون من الممكن تنفيذ سحر SwiftUI في Dart ، لكن الإعداد المعتاد المطلوب كبير جدًا وبطيء جدًا.

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

تحرير: أعتقد أن flutter generate كان خطوة في اتجاه جيد ، عار لأنه تم إزالته.

أعتقد أن هذا سيكون سؤالًا مثيرًا للاهتمام لطرحه في استطلاع Flutter Developer التالي.

ستكون بداية جيدة. قسّم هذه المشكلة إلى أجزاء / أسئلة مختلفة واكتشف ما إذا كانت هذه مشكلة حقيقية يرغب مطورو Flutter في حلها.

بمجرد أن يصبح الأمر واضحًا ، ستكون هذه المحادثة أكثر طلاقة وإثراءًا

من وجهة نظري ، أشعر أنني قضيت ساعات في كتابة تعليقات مفصلة تعرض المشكلات المختلفة والإجابة على الأسئلة ، لكن تعليقاتي تم رفضها وطرح السؤال نفسه مرة أخرى.

إذا كنت أطرح نفس الأسئلة ، فذلك لأنني لا أفهم الإجابات.

على سبيل المثال ، العودة إلى تعليقك السابق (https://github.com/flutter/flutter/issues/51752#issuecomment-670959424):

المشكلة التي تمت مناقشتها هي:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

هذا الرمز غير قابل للقراءة.

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

لماذا هذا غير قابل للقراءة؟

لأكون واضحًا ، من الواضح أنك تجده غير قابل للقراءة ، لا أحاول أن أقول إنك مخطئ. أنا فقط لا أفهم لماذا.

يمكننا إصلاح مشكلة قابلية القراءة عن طريق إدخال كلمة رئيسية جديدة تعمل على تغيير بناء الجملة إلى:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

هذه الشفرة أكثر قابلية للقراءة ، ولا علاقة لها بالخطافات ، ولا تعاني من قيودها.

إنه بالتأكيد أقل إسهابًا. لست متأكدًا من أنه أكثر قابلية للقراءة ، على الأقل بالنسبة لي. هناك المزيد من المفاهيم (الآن لدينا عناصر واجهة مستخدم وميزة "الكلمة الرئيسية" هذه) ؛ المزيد من المفاهيم يعني المزيد من الحمل المعرفي. (من المحتمل أيضًا أن يكون أقل كفاءة ، اعتمادًا على مدى استقلالية هذه الكائنات ؛ على سبيل المثال ، إذا تغيرت الرسوم المتحركة دائمًا أكثر من القيمة القابلة للاستماع والبث ، فنحن الآن نعيد بناء ValueListenableBuilder و StreamBuilder على الرغم من أنهما لن يتم تشغيلهما في العادة ؛ أيضًا يجب إدخال منطق التهيئة الآن وتخطي كل بناء.)

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

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

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

أفهم أن هناك رغبة في إعادة استخدام الكود بطريقة أسهل. أنا فقط لا أعرف ماذا يعني ذلك.

كيف يمكن مقارنة ما يلي في سهولة القراءة بالنسختين أعلاه؟

Widget build(context) {
  return
    ValueListenableBuilder(valueListenable: someValueListenable, builder: (context, value, _) =>
    StreamBuilder(stream: someStream, builder: (context, value2) =>
    TweenAnimationBuilder(tween: Tween(...), builder: (context, value3) =>
    Text('$value $value2 $value3'),
  )));
}

szotp إذا كان هناك الكثير من الاحتكاك حول حل الكود البرمجي الحالي ، فالرجاء عدم التردد في إرسال خطأ لطلب التحسينات هناك.

jamesblasco لا أعتقد أن هناك أي شك في أن هناك مشكلة حقيقية هنا يريد الناس حلها. السؤال بالنسبة لي هو بالضبط ما هي هذه المشكلة ، حتى نتمكن من تصميم حل.

يمكنني الرد على المخاوف بشأن عيوب الخطافات أو المطلوب تضمينه في الكود ، لكنني لا أعتقد أن هذا ما يجب أن نركز عليه الآن.

يجب أن نتفق أولاً على ماهية المشكلة. إذا لم نفعل ذلك ، فأنا لا أرى كيف يمكننا الاتفاق على مواضيع أخرى.

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

أعتقد أن جزءًا كبيرًا من المشكلة هنا هو أن الطريقة التي تُبرمج بها تختلف اختلافًا جذريًا عن طريقة البرمجة التي يقوم بها معظم الأشخاص.

على سبيل المثال ، Flutter ومثال التطبيق الذي قدمته لكليهما:

  • لا تستخدم dartfmt
  • استخدم always_specify_types

مع هاتين النقطتين فقط ، سأفاجأ إذا كان ذلك يمثل أكثر من 1٪ من المجتمع.

على هذا النحو ، من المحتمل أن يكون ما تقيمه على أنه مقروء مختلفًا تمامًا عما يعتقده معظم الناس أنه مقروء.

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

لماذا هذا غير قابل للقراءة؟

توصيتي هي تحليل المكان الذي تنظر إليه عينك عند البحث عن شيء معين ، وعدد الخطوات التي يجب اتخاذها للوصول إلى هناك.

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

هل من الأسهل العثور على إجابة لاستخدام البنية الخطية أو البنية المتداخلة؟

الأسئلة:

  • ما هو عنصر واجهة المستخدم non-builder الذي يتم إرجاعه بواسطة طريقة الإنشاء هذه؟
  • من الذي أنشأ المتغير bar ؟
  • كم بناة لدينا؟

باستخدام بناة:

 بناء الأداة (السياق) {
 إرجاع ValueListenableBuilder(
 valueListenable: someValueListenable ،
 باني: (سياق ، فو ، _) {
 إرجاع StreamBuilder(
 تيار: بعض البث ،
 باني: (سياق ، باز) {
 عودة TweenAnimationBuilder(
 توين: توين (...) ،
 باني: (سياق ، شريط) {
 حاوية الإرجاع () ؛
 } ،
 ) ؛
 } ،
 ) ؛
 } ،
 ) ؛
 }

باستخدام صيغة خطية:

 بناء الأداة (السياق) {
 final foo = الكلمة الأساسية ValueListenableBuilder (valueListenable: someValueListenable) ؛
 الشريط الأخير = الكلمة الأساسية StreamBuilder (تيار: SomeStream) ؛
 final baz = الكلمة الأساسية TweenAnimationBuilder (tween: Tween (...)) ؛

عودة الصورة () ؛ }


في حالتي ، أجد صعوبة في البحث في الشفرة المتداخلة للعثور على الإجابة.
من ناحية أخرى ، فإن العثور على الإجابة باستخدام الشجرة الخطية يكون فوريًا

لقد ذكرت أنك تريد تقليل المسافة البادئة ، لكنك وصفت إصدار استخدام المنشئين دون المسافة البادئة بأنه غير قابل للقراءة أيضًا ، لذلك من المفترض أن المشكلة ليست المسافة البادئة في النص الأصلي.

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

بتجاهل حقيقة أن المثال لا يعمل ، لا أمانع في تقسيمه لشرح سبب عدم قراءته. هل سيكون ذلك ذا قيمة؟

"" دارت
بناء الأداة (السياق) {
إرجاع
ValueListenableBuilder (valueListenable: someValueListenable ، منشئ: (سياق ، قيمة ، _) =>
StreamBuilder (دفق: SomeStream ، منشئ: (سياق ، قيمة 2) =>
TweenAnimationBuilder (توين: توين (...) ، المنشئ: (السياق ، القيمة 3) =>
نص ('value $ value2 $ value3') ،
))) ؛
}

هذا يبدو أفضل.
ولكن هذا على افتراض أن الناس لا يستخدمون dartfmt

مع dartfmt ، لدينا:

Widget build(context) {
  return ValueListenableBuilder(
      valueListenable: someValueListenable,
      builder: (context, value, _) => StreamBuilder(
          stream: someStream,
          builder: (context, value2) => TweenAnimationBuilder(
                tween: Tween(),
                builder: (context, value3) => Text('$value $value2 $value3'),
              )));
}

والذي لا يختلف تقريبًا عن الكود الأصلي.

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

هذه تفاصيل التنفيذ.
لا يوجد سبب معين لامتلاك عنصر أم لا.
في الواقع ، قد يكون من المثير للاهتمام أن يكون لديك عنصر ، حتى نتمكن من تضمين LayoutBuilder وربما GestureDetector .

أعتقد أنها ذات أولوية منخفضة. لكن في مجتمع React ، من بين مكتبات الخطافات المختلفة ، رأيت:

  • useIsHovered - إرجاع قيمة منطقية تخبر ما إذا كان عنصر واجهة المستخدم يحوم أم لا
  • useSize - (يجب أن يكون useContraints في Flutter) والذي يعطي حجم واجهة المستخدم المرتبطة.

(من المحتمل أيضًا أن يكون أقل كفاءة ، اعتمادًا على مدى استقلالية هذه الكائنات ؛ على سبيل المثال ، إذا تغيرت الرسوم المتحركة دائمًا أكثر من القيمة القابلة للاستماع والبث ، فنحن الآن نعيد بناء ValueListenableBuilder و StreamBuilder على الرغم من أنهما لن يتم تشغيلهما في العادة ؛ أيضًا يجب إدخال منطق التهيئة الآن وتخطي كل بناء.)

هذا يعتمد على كيفية حل الحل.

إذا ذهبنا لإصلاح اللغة ، فلن تكون هذه المشكلة مشكلة على الإطلاق.

يمكننا أن نجعل ذلك:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

"تجميع" إلى:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

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

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

ليس من المستحيل إعادة تقييم جميع الخطافات بشكل أسرع. يبدو أن هذا كان الاستنتاج الذي توصل إليه فريق React عند تصميم الخطافات.
قد لا ينطبق هذا على Flutter بالرغم من ذلك.

لماذا هذا غير قابل للقراءة؟

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

هذا يشبه إلى حد كبير غير المتزامن / انتظار السكر مقابل العمل مع Future API الخام ، مفهوم قائم على الاستمرار تحت (وحتى الحجج المؤيدة والمعارضة متشابهة جدًا) - نعم ، يمكن استخدام واجهة برمجة التطبيقات المستقبلية بشكل مباشر ولا تخفي أي شيء ، ولكن قابلية القراءة وقابلية الصيانة بالتأكيد ليس جيدًا - المتزامن / الانتظار هو الفائز هناك.

توصيتي هي تحليل المكان الذي تنظر إليه عينك عند البحث عن شيء معين ، وعدد الخطوات التي يجب اتخاذها للوصول إلى هناك.

لقد كنت أبرمج لمدة 25 عامًا حتى الآن بأكثر من 10 لغات مختلفة ، وهذه أسهل الطرق لتقييم ما يجعل الكود قابلاً للقراءة. تعد قابلية قراءة الكود المصدري أمرًا صعبًا ، ولكنه يتعلق أكثر بمدى جودة تعبيره عن مفاهيم البرمجة ومنطقها ، بدلاً من "المكان الذي تنظر فيه عيناي" أو عدد سطور التعليمات البرمجية التي يستخدمها.

أو بالأحرى ، يبدو لي أنكم تركزون كثيرًا على قابلية القراءة وأقل على الصيانة .

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


final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);

ماذا ستكون القيمة؟ القطعة؟ متغير سلسلة؟ أعني أنه يتم استخدامه داخل ملف
return Text('$value $value2 $value3');

ما تريده أساسًا هو أنه بالإشارة إلى المتغير A في طريقة إنشاء عنصر واجهة المستخدم B ، يجب أن يتسبب في إعادة إنشاء B كلما تغيرت قيمة A؟ هذا ما يفعله mobx حرفيًا ، وهو يفعل ذلك بالضبط بالقدر المناسب من السحر / النمطي.


final value2 = keyword StreamBuilder(stream: someStream);

ماذا يجب أن يعود هذا؟ القطعة؟ مجرى؟ قيمة سلسلة؟

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

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


final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

فئة تسمى "TweenAnimationBuilder" إرجاع سلسلة ؟! أنا لا أقترب حتى من سبب كونها فكرة رهيبة.

لا يوجد فرق في المسافة البادئة / سهولة القراءة بين:

Future<double> future;

AsyncSnapshot<double> value = keyword FutureBuilder<double>(future: future);

و:

Future<double> future;

double value = await future;

كلاهما يفعل نفس الشيء بالضبط: الاستماع إلى كائن وفك قيمته.

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

يمكن تطبيق نفس الحجة على سلاسل الوعد / المستقبل.

foo().then(x =>
  bar(x).then(y =>
    baz(y).then(z =>
      qux(z)
    )
  )
)

ضد

let x = await foo();
let y = await bar(x);
let z = await baz(y);
await qux(z);

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

فئة تسمى "TweenAnimationBuilder" إرجاع سلسلة ؟! أنا لا أقترب حتى من سبب كونها فكرة رهيبة.

يمكنك تقديم نفس الحجة حول الوعود / العقود الآجلة ، والقول إن await يحجب حقيقة أنه يعيد الوعد.

يجب أن أشير إلى أن فكرة "فك تغليف" الأشياء من خلال النحو ليست جديدة. نعم ، في اللغات السائدة تأتي عبر async / wait ، ولكن ، على سبيل المثال ، F # لها تعبيرات حسابية ، على غرار التدوين do في بعض لغات FP المتشددة. هناك ، لديها الكثير من القوة ويتم تعميمها للعمل مع أي أغلفة تفي بقوانين معينة. أنا لا أقترح إضافة Monads إلى Dart ، لكنني أعتقد أنه من الجدير ذكر أن هناك بالتأكيد سابقة لبناء الجملة من النوع الآمن لـ "فك التفاف" الأشياء التي لا تتوافق بالضرورة مع الاستدعاءات غير المتزامنة.

بالتراجع خطوة إلى الوراء ، أعتقد أن أحد الأشياء التي يعاني منها كثير من الناس هنا (بمن فيهم أنا) هو هذا السؤال حول قابلية القراءة. كما ذكر rousselGit ، كان هناك الكثير من الأمثلة خلال سلسلة مشكلات قابلية القراءة مع النهج الحالي القائم على Builder . بالنسبة للكثيرين منا ، يبدو واضحًا أن هذا:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

أقل قابلية للقراءة من هذا:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

لكن من الواضح أنه ليس واضحًا بذاته ، نظرًا لأن Hixie و Rudiksz غير مقتنعين (أو يعارضان بشكل نشط) فكرة أن الثانية أكثر قابلية للقراءة من الأولى.

إذن ، هذا هو تفصيلي (مهما كان المبلغ الصغير الذي يستحقه) حول سبب كون كتلة الكود الثانية أكثر قابلية للقراءة من الأولى:

1. تم وضع مسافة بادئة أكبر بكثير من الثانية

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


في الجزء الثاني من الكود ، لا توجد مسافة بادئة تخفف من المشكلة.

2. تتطلب كتلة التعليمات البرمجية الأولى مزيدًا من بناء الجملة للتعبير عن نيتها

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


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

3. يخفي أول جزء من الكود الجزء الأكثر أهمية من طريقة build في أعمق جزء من التداخل

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


في كتلة التعليمات البرمجية الثانية ، يكون العرض الفعلي الذي يمكن عرضه Widget الذي يتم إرجاعه في الجزء السفلي من الوظيفة ، وهو ما يتضح على الفور. علاوة على ذلك ، وجدت أنه عندما يكون لديك شيء مثل بناء الجملة المقترح OP ، يمكنك الاعتماد على المرئي Widget دائمًا في أسفل الوظيفة ، مما يجعل الكود أكثر قابلية للتنبؤ به وسهل القراءة.

فيما يتعلق بالتداخل ، هناك فرق بين التعبير عن التداخل والتعبير عن التداخل .

في حالة تداخل View -> Text العادي وما شابه ، يكون التداخل مهمًا لأنه يمثل العلاقات بين الوالدين والطفل على الشاشة. بالنسبة إلى ميزات مثل السياق (لست متأكدًا مما إذا كان يحتوي على Flutter) ، فإنه يمثل نطاق السياقات. لذا فإن التداخل نفسه له معنى دلالي مهم في تلك الحالات ولا يمكن تجاهله. لا يمكنك فقط تبديل مكان الوالدين والطفل وتوقع أن تكون النتيجة واحدة.

ومع ذلك ، مع تداخل البنائين (المعروف أيضًا باسم "Render Props" في React) ، أو تداخل الوعود ، من المفترض أن ينقل التعشيش سلسلة من التحولات / التعزيزات . الشجرة نفسها ليست بنفس الأهمية - على سبيل المثال ، عند تداخل ABuilder -> BBuilder -> CBuilder بشكل مستقل ، فإن العلاقات بين الوالدين والطفل لا تنقل معنى إضافيًا.

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

هذا هو السبب في أن async/await مقنع. إنه يزيل المعلومات الإضافية (علاقة الوعد بالوالدين) التي تصف آلية منخفضة المستوى ، وبدلاً من ذلك تتيح لك التركيز على النية عالية المستوى (وصف التسلسل).

الشجرة هي بنية أكثر مرونة من القائمة. ولكن عندما يكون لكل والد طفل واحد فقط ، تصبح شجرة مرضية - قائمة بالأساس. يدرك Async / Await و Hooks أننا نهدر بناء الجملة على شيء لا ينقل المعلومات ، ونزيلها.

هذا في الواقع مثير للاهتمام لأنني قلت سابقًا "هذا لا يتعلق بالنموذج المعياري" والآن يبدو أنني أعارض نفسي. أعتقد أن هناك شيئين هنا.

في حد ذاته ، البناة (أو على الأقل Render Props in React) هم الحل (AFAIK) لمشكلة "المنطق القابل لإعادة الاستخدام". إنها فقط ليست مريحة للغاية إذا كنت تستخدم الكثير منها. من الطبيعي أن تثبط عزيمتك بسبب المسافة البادئة لاستخدام أكثر من 4 أو 5 منهم في نفس المكون. كل مستوى تالي هو نتيجة قراءة.

لذا فإن الجزء الذي يبدو أنه لم يتم حله بالنسبة لي هو تقليل تكلفة القارئ للتداخل. وهذه الوسيطة هي بالضبط نفس الوسيطة المستخدمة في async / await .

إنه غير مقروء للأسباب التالية:

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

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

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

قارن الآن بهذا ، فإن الإبراز هو أساسًا زائدة عن الحاجة ، لأنه لا يوجد مكان آخر تذهب إليه عيني:
image

تجدر الإشارة إلى أنه من المحتمل أن يكون هناك خلاف في المقروئية مقابل القابلية للتذمر. من المحتمل أن يقضي

كمرجع ، ما يعادل السياق في React هو InheritedWidgets / Provider

الاختلاف الوحيد بينهما هو ، في React قبل الخطافات ، نحن _had_ لاستخدام نمط Builder لاستهلاك سياق / عنصر موروث

في حين أن Flutter لديه طريقة لربط إعادة البناء باستدعاء وظيفة بسيط.
لذلك ليست هناك حاجة للخطافات لتسوية الأشجار باستخدام InheritedWidgets - أي نوع من الحلول لحل مشكلة البناة

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

ولكن من الجدير بالذكر أن تقديم حل يشبه الخطاف من شأنه أن يحل كلا من https://github.com/flutter/flutter/issues/30062
و https://github.com/flutter/flutter/issues/12992

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

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

طريقة أخرى للنظر إليها ، هي مقدار الشفرة التي يحتاجها عقلي لتنقيحها بصريًا. بالنسبة لي ، يمثل هذا بدقة "العمل الشاق" الذي يجب على عقلي القيام به قبل أن أتمكن من تحليل ما يتم إرجاعه من الشجرة:
image

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

فكر في عيني على أنها وحدة معالجة مركزية جائعة ، أيها أكثر ملاءمة للمعالجة؟ :)

في حالة التداخل العادي View -> Text التداخل ، يكون التداخل مهمًا لأنه يمثل _العلاقات بين الوالدين والطفل_ على الشاشة. بالنسبة إلى ميزات مثل السياق (لست متأكدًا مما إذا كان يحتوي على Flutter) ، فإنه يمثل نطاق السياقات. لذا فإن التداخل نفسه له معنى دلالي مهم في تلك الحالات ولا يمكن تجاهله. لا يمكنك فقط تبديل مكان الوالدين والطفل وتوقع أن تكون النتيجة واحدة.

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

هناك مشكلتان تقفزان إلي هنا.

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

  2. البناء هو عبارة عن مزامنة ، ولكن الأشياء الأكثر إثارة للاهتمام التي تتعامل معها كمطور تطبيقات غير متزامنة. بالطبع لا يمكننا جعل البناء غير متزامن. لقد أنشأنا وسائل الراحة هذه مثل Stream / Animation / FutureBuilder لكنها لا تعمل دائمًا بشكل جيد بما يكفي لتلبية احتياجات المطور. من المحتمل أننا لا نستخدم Stream أو FutureBuilder كثيرًا في إطار العمل.

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

fwiw ، تناول فريق React إعادة استخدام مشكلة قابلية القراءة باعتبارها الدافع الأول:
دافع الخطافات: من الصعب إعادة استخدام المنطق القائم على الحالة بين المكونات
لا تقدم React هذه الأنماط تتطلب منك إعادة هيكلة مكوناتك عند استخدامها ، الأمر الذي قد يكون مرهقًا ويصعب اتباع التعليمات البرمجية .

هذا مشابه جدًا للطريقة التي لا يقدم بها Flutter حاليًا أي طريقة لـ "حالة الإنشاء" بشكل أصلي. قالت الشجرة إن هذا يردد أيضًا ما يحدث عندما نقوم نحن البنائين بتعديل شجرة التخطيط الخاصة بنا وجعل العمل معها أكثر تعقيدًا و "صعوبة في المتابعة".

dnfield إذا كان يجب استدعاء build كل مرة ، فربما يمكننا جعل الخطافات ليست في طريقة build بحيث تتم مزامنة هذا الإصدار دائمًا ، أي ضعهم داخل الفصل حيث initState و dispose هي. هل هناك مشاكل في القيام بذلك ، من أولئك الذين يكتبون الخطافات؟

يمكنك تقديم نفس الحجة حول الوعود / العقود الآجلة ، والقول إن await يحجب حقيقة أنه يُرجع وعدًا.

لا أنت لا تفعل. الانتظار هو حرفيا مجرد سكر نحوي لميزة واحدة. في حالة استخدامك لـ Futures المطول ، أو الصيغة التعريفية ، فإن __intent__ في الكود هو نفسه.

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

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

أفهم من المناقشة أن معظم المطورين هنا لا يحبون أن يزعجوا أنفسهم بهذه الأنواع من التفاصيل ويريدون تطويرًا سهلًا ، من خلال القدرة على استخدام هذه "الخطافات" دون الحاجة إلى القلق بشأن تفاصيل التنفيذ.
سيؤدي استخدام ما يسمى بـ "الخطافات" دون فهم تداعياتها الكاملة إلى شفرة غير فعالة وسيئة ، وسيؤدي إلى إطلاق النار على أنفسهم - وبالتالي لا يحل مشكلة "حماية المطورين المبتدئين".

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

Rudiksz مرة أخرى ، لا أحد يجبرك على الانتقال إلى الخطافات. سيظل النمط الحالي موجودًا. ما هي حجتك في مواجهة هذه المعرفة؟

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

Rudiksz مرة أخرى ، لا أحد يجبرك على الانتقال إلى الخطافات. سيظل النمط الحالي موجودًا. ما هي حجتك في مواجهة هذه المعرفة؟

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

سأكون صريحا جدا هنا.
لا تتعلق هذه المشكلة حقًا بـ hooks و statefulwidget ومن يستخدم ماذا ، بل تتعلق بالعودة لعقود من أفضل الممارسات حتى يتمكن بعض الأشخاص من كتابة 5 أسطر من التعليمات البرمجية أقل.

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

لست متأكدًا من مصدر أفضل ممارساتك ، لأنني أعلم أن الاحتفاظ بالشفرة سهلة القراءة هو أفضل ممارسة ، وأن التداخل المفرط هو نمط مضاد. ما هي أفضل الممارسات التي تشير إليها بالضبط؟

fwiw ، تناول فريق React إعادة استخدام مشكلة قابلية القراءة باعتبارها الدافع الأول:
دافع الخطافات: من الصعب إعادة استخدام المنطق القائم على الحالة بين المكونات
لا تقدم React هذه الأنماط تتطلب منك إعادة هيكلة مكوناتك عند استخدامها ، الأمر الذي قد يكون مرهقًا ويصعب اتباع التعليمات البرمجية .

أسمع الجميع يهتفون حول مدى روعة Flutter أكثر من React. ربما لأنه لا يفعل كل شيء بالطريقة التي يعمل بها React؟ لا يمكنك الحصول عليه في كلا الاتجاهين ، ولا يمكنك القول إن Flutter يسبق الأميال عن React وتطلب أيضًا أن يفعل كل شيء تمامًا مثل React.

أيًا كان الحل الذي يقرر Flutter استخدامه لمشكلة معينة ، يجب أن يكون قائما على مزاياه الخاصة. لست على دراية بـ React ولكن يبدو أنني أفتقد بعض التكنولوجيا المذهلة حقًا. : /

لا أعتقد أن أي شخص يجادل في أن Flutter يجب أن تفعل كل شيء مثل React.

لكن الحقيقة هي أن طبقة عناصر واجهة Flutter مستوحاة بشكل كبير من React. جاء ذلك في الوثائق الرسمية.
ونتيجة لذلك ، فإن الأدوات المصغّرة لها نفس الفوائد والمشكلات نفسها التي تتمتع بها مكونات React.

هذا يعني أيضًا أن React لديها خبرة أكثر من Flutter في التعامل مع هذه المشكلات.
واجهتهم لفترة أطول ، وتفهمهم بشكل أفضل.

لذلك لا ينبغي أن يكون مفاجئًا أن تتشابه حلول مشاكل Flutter مع حلول مشاكل React.

واجهة برمجة تطبيقات Rudiksz Flutter الخاصة بالمستخدم تشبه إلى حد بعيد النموذج المعتمد على فئة

نظرًا للتشابه بينهما ، لا ينبغي أن يكون مفاجئًا أن تبدو حلول المشكلات متشابهة ، كما ذكر أعلاه.

من فضلك ، دعونا نبذل قصارى جهدنا لعدم القتال مع بعضنا البعض.

الشيء الوحيد الذي سيقودنا إليه القتال هو قتل هذا النقاش والفشل في إيجاد حل.

أسمع الجميع يهتفون حول مدى روعة Flutter أكثر من React. ربما لأنه لا يفعل كل شيء بالطريقة التي يعمل بها React؟ لا يمكنك الحصول عليه في كلا الاتجاهين ، ولا يمكنك القول إن Flutter يسبق الأميال عن React وتطلب أيضًا أن يفعل كل شيء تمامًا مثل React.

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

لا أميل إلى أي شيء ، لم أعمل أبدًا في React ، وأنا أحب Flutter. يمكنني رؤية المشكلة بسهولة هنا.

Rudiksz لا يمكننا التأكد مما إذا كان أداءً عمليًا حتى نضعه في الممارسة. ليس من السهل اتخاذ القرار الآن.

Hixie هذا مثال لرحلة قد يكون لدى مستخدم الرفرفة الشائعة لتنفيذ عنصر واجهة مستخدم لإظهار لقب المستخدم من معرف المستخدم مع كل من HookWidget و StatefulWidget .

__ أداة الخطاف__

String useUserNickname(Id userid) {
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    name.value = await fetchNicknames()[userId;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__القطعة الأصلية__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";

   initState() {
     super.initState();
     fetchAndUpdate();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      final result = await fetchNicknames()[widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

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

  • نقل استدعاء fetchNicknames() logic إلى عنصر واجهة المستخدم الأصل وحفظ النتيجة.
  • باستخدام مدير ذاكرة التخزين المؤقت.

الحل الأول مقبول ولكن به مشكلتان.
1 - يجعل UserNickname عديم الفائدة لأنه الآن أداة نصية فقط ، وإذا كنت تريد استخدامه في مكان آخر ، فعليك تكرار ما فعلته في الأداة الأم (التي تحتوي على ListView ) . منطق إظهار الاسم المستعار ينتمي إلى UserNickname لكن علينا نقله بشكل منفصل.
2 - قد نستخدم fetchNicknames() في العديد من الأشجار الفرعية الأخرى ومن الأفضل تخزينها مؤقتًا لجميع التطبيقات وليس لجزء واحد فقط من التطبيق.

لذا تخيل أننا نختار مدير ذاكرة التخزين المؤقت ونوفر فئة CacheManager بـ InheritedWidgets أو Provider .

بعد إضافة دعم التخزين المؤقت:

__ أداة الخطاف__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__القطعة الأصلية__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

لدينا خادم مقبس يخطر العملاء عندما تتغير الأسماء المستعارة.

__ أداة الخطاف__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final notifications = Provider.of<ServiceNotifications>(context);
  final name = useState("");

  fetchData() async {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
   }

  useEffect(() {
     final sub = notifications.on("nicknameChanges", fetchData);
     return () => sub.unsubscribe();
   }, [])

  useEffect(fetchData, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__القطعة الأصلية__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;
  ServerNotification notifications;
  CancelableOperation sub;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
     this.notifications = Provider.of<ServerNotification>(context);
     this.sub = notifications.on("nicknameChanges", fetchAndUpdate);
   }

   dispose() {
      super.dispose();
      this.sub.unsubscribe();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

حتى الآن كلا التنفيذ مقبول وجيد. IMO النموذج المتداول في الحالة الثابتة لا يمثل مشكلة على الإطلاق. تنشأ المشكلة عندما نحتاج إلى عنصر واجهة مستخدم مثل UserInfo يحتوي على كنية المستخدم والصورة الرمزية. كما لا يمكننا استخدام عنصر واجهة المستخدم UserNickname لأننا نحتاج إلى إظهاره في جملة مثل "مرحبًا [اسم المستخدم]".

__ أداة الخطاف__

useFetchUserNickname(userId) // old code
useUserAvatar(userId) // implementation like `useFetchUserNickname`

class UserNickname extends HookWidget {
  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    final avatar = useUserAvatar(userId);
    return Row(
      children: [Image.network(avatar), Text(nickname)],
    );
  }
}

ولكن بالنسبة إلى الأداة __stateful___ لا يمكننا استخدام المنطق الذي كتبناه فقط. يتعين علينا نقل المنطق إلى فصل دراسي (مثل Property الذي اقترحته) وما زلنا بحاجة إلى كتابة صمغ عنصر واجهة المستخدم مع فئة الخاصية مرة أخرى في عنصر واجهة المستخدم الجديد.

إذا رأيت التغييرات في المثال الثالث الأول ، فإننا لم نغير الأداة نفسها على الإطلاق لأن التغييرات الضرورية الوحيدة كانت في منطق الحالة والمكان الوحيد الذي تغير هو منطق الحالة.
أعطانا هذا منطق حالة نظيفًا (عنيدًا) وقابلًا للتكوين وقابل لإعادة الاستخدام تمامًا يمكننا استخدامه في أي مكان.

IMHO المشكلة الوحيدة هي استدعاء useUserNickname وهو أمر مخيف لأن وظيفة واحدة يمكن أن تفعل هذا كثيرًا.
ولكن في سنوات خبرتي في التفاعل واستخدام flutter_hooks في تطبيقين قيد الإنتاج (يستخدمان الخطافات بكثافة) يثبت عدم وجود إدارة جيدة للحالة (لقد جربت أيضًا MobX وحلول إدارة الدولة الأخرى ولكن الصمغ الموجود في القطعة موجود دائمًا) أكثر ترويعًا. لست بحاجة إلى كتابة مستندات من 5 صفحات لكل شاشة في تطبيق واجهة أمامية قد أحتاج إلى إضافة بعض الميزات الصغيرة في غضون أشهر قليلة بعد الإصدار الأول حتى أفهم كيفية عمل صفحة من تطبيقي. التطبيق يستدعي قطع الكثير؟ مهمة سهلة ، أذهب إلى الخطاف ذي الصلة وقم بتغييره وإصلاحات التطبيق بالكامل لأن التطبيق بأكمله يستخدم هذا الخطاف. يمكننا الحصول على أشياء متشابهة في التطبيقات دون استخدام الخطافات مع تجريد جيد ولكن ما أقوله هو أن الخطافات هي تلك التجريد الجيد.

أنا متأكد من أن gaearon يمكنه أن

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

من الممكن أيضًا تحديث الشجرة الفرعية فقط التي نحتاج إلى تحديثها مثل StreamBuilder عندما يكون هناك الكثير من التحديثات (مثل الرسوم المتحركة) باستخدام:

1 - ببساطة إنشاء عنصر واجهة مستخدم جديد يكون خيارًا قابلاً للتطبيق تمامًا لكل من HookWidget و StatefulWidget/StatelessWidget
2 - استخدام شيء مشابه لحزمة HookWidgetBuilder في flutter_hooks لأن بيانات أدوات الوالدين والأولاد مرتبطة بإحكام شديد.

ملاحظة جانبية: أنا حقًا أقدر Hixie و rousselGit لمناقشة هذا الموضوع ووضع هذا القدر من الطاقة في هذه القضية. إنني أتطلع حقًا إلى نتيجة هذه المحادثات.

أنا أتوصل إلى شيء رائع / أنيق على ما أعتقد ، بناءً على نقطة انطلاق التفاح: التفاح بدلاً من الخطافات التي تبدو غريبة جدًا.

لذا ، تخيل أن لدينا عنصرًا مميزًا مع هذا التوقيع:

class ExampleSimple extends StatefulWidget {
  final Duration duration1;
  final Duration duration2;
  final Duration duration3;

  const ExampleSimple({Key key, this.duration1, this.duration2, this.duration3}) : super(key: key);

  <strong i="9">@override</strong>
  _ExampleSimpleState createState() => _ExampleSimpleState();
}

إذا أردنا تنفيذ الحالة باستخدام وحدات تحكم الرسوم المتحركة الفانيليا ، فسنحصل على شيء مثل:

class _ExampleSimpleVanillaState extends State<ExampleSimpleVanilla> with SingleTickerProviderStateMixin {
  AnimationController _anim1;
  AnimationController _anim2;
  AnimationController _anim3;

  <strong i="13">@override</strong>
  void initState() {
    _anim1 = AnimationController(duration: widget.duration1, vsync: this);
    _anim1.forward();
    _anim2 = AnimationController(duration: widget.duration2, vsync: this);
    _anim2.forward();
    _anim3 = AnimationController(duration: widget.duration3, vsync: this);
    _anim3.forward();
    super.initState();
  }

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.value * 20, horizontal: _anim3.value * 30,),
      color: Colors.red.withOpacity(_anim1.value),
    );
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(ExampleSimpleVanilla oldWidget) {
    if (oldWidget.duration1 != widget.duration1) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration2 != widget.duration2) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration3 != widget.duration3) {
      _anim1.duration = widget.duration1;
    }
    super.didUpdateWidget(oldWidget);
  }

  <strong i="16">@override</strong>
  void dispose() {
    _anim1.dispose();
    _anim2.dispose();
    _anim3.dispose();
    super.dispose();
  }
}

إذا أنشأناها باستخدام StatefulProperty ، فإننا ننتج شيئًا مثل هذا:

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationProperty _anim2;
  StatefulAnimationProperty _anim3;

  <strong i="6">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim2, StatefulAnimationProperty(duration: widget.duration2, playOnInit: true));
    _anim3 = initProperty(_anim3, StatefulAnimationProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="7">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.controller.value * 20, horizontal: _anim3.controller.value * 30,),
      color: Colors.red.withOpacity(_anim1.controller.value),
    );
  }
}

بعض الملاحظات حول الاختلافات هنا:

  1. خارج الجزء العلوي ، أحدهما 20 سطرًا والآخر 45. أحدهما 1315 حرفًا والآخر 825. فقط 3 أسطر و 200 حرف مهم في هذه الفئة (ما يحدث في الإصدار) ، لذلك يعد هذا بالفعل تحسنًا هائلاً في الإشارة : نسبة
  2. تحتوي خيارات الفانيليا على نقاط متعددة حيث يمكن إنشاء الأخطاء. ننسى التخلص ، أو ننسى التعامل مع didChange ، أو ارتكاب خطأ في didChange ، ولديك خطأ في قاعدة التعليمات البرمجية الخاصة بك. يزداد هذا الأمر سوءًا عند استخدام أنواع متعددة من وحدات التحكم. ثم لديك وظائف واحدة وهي تمزيق الكائنات من جميع الأنواع المختلفة ، والتي لن يتم تسميتها بشكل جيد وبالتتابع هكذا. يصبح ذلك فوضويًا ومن السهل جدًا ارتكاب الأخطاء أو تفويت الإدخالات.
  3. لا يوفر خيار الفانيليا أي طريقة لإعادة استخدام الأنماط الشائعة أو المنطق ، مثل playOnInit ، لذلك لا بد لي من تكرار هذا المنطق ، أو إنشاء بعض الوظائف المخصصة في فئة واحدة جدًا تريد استخدام Animator.
  4. ليست هناك حاجة لفهم SingleTickerProviderMixin هنا ، وهو "السحر" والتشويش على ماهية المؤشر بالنسبة لي لأشهر (في الإدراك المتأخر ، كان يجب أن أقرأ الفصل للتو ، ولكن كل درس تعليمي يقول فقط: أضف هذا المزيج السحري). هنا يمكنك إلقاء نظرة مباشرة على التعليمات البرمجية المصدر الخاصة بـ StatefulAnimationProperty ، ومعرفة كيف تستخدم وحدات التحكم في الرسوم المتحركة موفر المؤشر بشكل مباشر وفي السياق.

يجب عليك بدلاً من ذلك فهم ما يفعله StatefulPropertyManager ، ولكن الأهم من ذلك تعلم هذا مرة واحدة وتطبيقه على كائنات من أي نوع ، SingleTickerProviderMixin مخصص أساسًا لاستخدام أدوات التحكم في الرسوم المتحركة ، وقد يكون لكل وحدة تحكم اختلاطها الخاص لتسهيل الاستخدام ، الأمر الذي يصبح فوضويًا. مجرد وجود "كائنات الحالة" المنفصلة التي تعرف كل هذه الأشياء (تمامًا كما يفعل المنشئ!) ، هو أكثر نظافة وأفضل.

سيبدو رمز StatefulAnimationProperty كما يلي:

class StatefulAnimationProperty extends BaseStatefulProperty<StatefulAnimationProperty> implements TickerProvider {
  final Duration duration;
  final TickerProvider vsync;
  final bool playOnInit;

  StatefulAnimationProperty({<strong i="7">@required</strong> this.duration, <strong i="8">@required</strong> this.vsync, this.playOnInit = false});

  AnimationController get controller => _controller;
  AnimationController _controller;

  Ticker _ticker;

  <strong i="9">@override</strong>
  Ticker createTicker(onTick) {
    _ticker ??= Ticker((elapsed)=>handleTick(elapsed, onTick));
    return _ticker;
  }

  handleTick(Duration elapsed, TickerCallback onTick) {
    managerWidget.buildWidget(); //TODO: This just calls setState() on the host widget. Is there some better way to do this?
    onTick(elapsed);
  }

  <strong i="10">@override</strong>
  void init(StatefulAnimationProperty old) {
    _controller = old?.controller ??
        AnimationController(
          duration: duration ?? Duration(seconds: 1),
          vsync: vsync ?? this,
        );
    if (playOnInit && old?.controller == null) {
      _controller.forward();
    }
    super.init(old);
  }

  <strong i="11">@override</strong>
  void update(StatefulAnimationProperty old) {
    if (duration != old.duration) {
      _controller.duration = duration;
    }
    if (vsync != old.vsync) {
      _controller.resync(vsync);
    }
    super.update(old);
  }

  <strong i="12">@override</strong>
  void dispose() {
    _controller?.dispose();
    _ticker?.dispose();
    super.dispose();
  }
}

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

  void initStatefulProperties({bool firstRun = false}) {
    _anim1.init(duration: widget.duration1, playOnInit: true);
    _anim2.init(duration: widget.duration2, playOnInit: true);
    _anim3.init(duration: widget.duration3, playOnInit: true);
    super.initStatefulProperties(firstRun: firstRun);
  }

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

إليك منظر عين الطائر ، مع إشارة مرجعية باللون الأحمر:
image

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

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

كما أنه يجعل من السهل إنشاء كائنات الحالة المخصصة الخاصة بك ، مثل مثال FetchUser المذكور سابقًا ، والذي يتطلب حاليًا بشكل أساسي منشئ.

أعتقد أن هذا سيكون سؤالًا مثيرًا للاهتمام لطرحه في استطلاع Flutter Developer التالي.

ستكون بداية جيدة. قسّم هذه المشكلة إلى أجزاء / أسئلة مختلفة واكتشف ما إذا كانت هذه مشكلة حقيقية يرغب مطورو Flutter في حلها.

بمجرد أن يصبح الأمر واضحًا ، ستكون هذه المحادثة أكثر طلاقة وإثراءًا

تعطي ردود أفعال الرموز التعبيرية الموجودة أسفل كل تعليق فكرة واضحة عما إذا كان المجتمع يرى هذا على أنه مشكلة أم لا. إن رأي المطورين الذين قرأوا أكثر من 250 تعليقًا طويلًا لهذه المشكلة يعني الكثير من imho.

esDotDev هذا مشابه لبعض الأفكار التي كنت أتعامل معها ، على الرغم من أنني أحب فكرتك المتمثلة في أن يكون العقار نفسه

الشيء الرئيسي الذي أعاني منه هو كيفية القيام بذلك بطريقة فعالة. على سبيل المثال ، تأخذ ValueListenableBuilder وسيطة فرعية يمكن استخدامها لتحسين الأداء بشكل قابل للقياس. لا أرى طريقة للقيام بذلك مع نهج الملكية.

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

ستحتاج الوثائق فقط إلى أن تعكس أفضل الممارسات وتوضح المفاضلات.

الشيء الرئيسي الذي أعاني منه هو كيفية القيام بذلك بطريقة فعالة. على سبيل المثال ، تأخذ ValueListenableBuilder وسيطة فرعية يمكن استخدامها لتحسين الأداء بشكل قابل للقياس. لا أرى طريقة للقيام بذلك مع نهج الملكية.

حسنًا ، أعتقد أن الهدف الكامل للخصائص هو للأشياء غير المرئية. إذا كان هناك شيء يريد أن يكون لديه فتحة سياق في الشجرة ، فيجب أن يكون هذا الشيء منشئًا (في الواقع هذه هي الأشياء الوحيدة التي يجب أن تكون بناة الآن على ما أعتقد؟)

لذلك سيكون لدينا StatefulValueListenableProperty نستخدمها معظم الوقت عندما نريد فقط ربط العرض بالكامل. لدينا بعد ذلك أيضًا ValueListenableBuilder في فرصة أننا نريد إعادة بناء قسم فرعي من شجرتنا.

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

TimWhiting جزء كبير من فلسفة تصميم

تضمين التغريدة
ماذا عن شيء مثل هذا للبناة؟

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationBuilderProperty _anim2;

  <strong i="7">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim3, StatefulAnimationBuilderProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="8">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red.withOpacity(_anim1.controller.value),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

هل يمكنك التفصيل؟ لست متأكدًا من فهمي للاقتراح.

أعتقد أنه يقول أن StatefulProperty يمكن أن يوفر طريقة بناء اختيارية للخصائص التي تحتوي على بعض المكونات المرئية:

return Column(
   children: [
      TopContent(),
      _valueProperty.build(SomeChildWidget()),
   ]
)

وهو تماما 🔥 imo ،

نعم ، لا أعرف ما إذا كان ذلك سيعمل ، لكن طريقة الإنشاء ستأخذ طفلًا تمامًا مثل المنشئ العادي ، باستثناء الخصائص الأخرى للمنشئ التي يتم تعيينها بواسطة الخاصية.
إذا كنت بحاجة إلى السياق من المنشئ ، فإن التابع build يقبل وسيطة builder التي توفر السياق.

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

افترض أن لديك هذا الرمز:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... كيف يمكنك تحويل ذلك؟

esDotDev تعجبني فكرتك المتمثلة في أن يكون العقار نفسه

إن أحد أقوى جوانب أسلوب الخطاف هذا ، هو أنه يمكنك - بشكل كامل - تغليف المنطق القائم على الحالة ، مهما كان ذلك. إذن في هذه الحالة ، القائمة الكاملة للتيار المتردد هي:

  1. قم بإنشاء مكيف الهواء
  2. امنحه مؤشرًا
  3. التعامل مع تغييرات الأداة
  4. معالجة تنظيف التيار المتردد والمؤشر
  5. إعادة بناء العرض على القراد

في الوقت الحالي ، يتم تقسيم الأشياء مع قيام المطورين بالتعامل (أو عدم التعامل) مع 1،3،4 يدويًا وبشكل متكرر ، بينما يتولى SingleTickerProviderMixin شبه السحري 2 و 5 (مع قيامنا بتمرير "هذا" كـ vsync ، الأمر الذي أربكني لأشهر! ). ومن الواضح أن SingleTickerProviderMixin نفسها هي محاولة لإصلاح هذا النوع من المشاكل ، وإلا فلماذا لا نذهب إلى النهاية ونجعلنا نطبق TickerProvider لكل فئة ، فسيكون الأمر أكثر وضوحًا.

افترض أن لديك هذا الرمز:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... كيف يمكنك تحويل ذلك؟

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulValueListenableBuilder _fooBuilder;

  <strong i="10">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _fooBuilder = initProperty(StatefulValueListenableProperty(valueListenable: widget.foo)); 
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return ExpensiveParent(
      child: SomethingInTheMiddle(
        _fooBuilder.value,
        _fooBuilder.builder(childBuilder: () => ExpensiveChild()),
      ),
    );
  }
}

تضمين التغريدة
شكرا على المثال. أعطيتها أفضل تسديداتي. ربما فاتني شيء.

من المهم ملاحظة أن المنشئ يخزن الطفل. سيكون السؤال هو متى يجب بالفعل إعادة بناء الطفل؟ أعتقد أن هذا هو السؤال الذي كنت تحاول طرحه ..

Hixie هل رأيت https://github.com/flutter/flutter/issues/51752#issuecomment -671104377
أعتقد أن هناك بعض النقاط الجيدة حقًا.
أبني اليوم شيئًا يحتوي على الكثير من ValueListenableBuilder ولا يمكنني إلا أن أقول أنه ليس من الجيد قراءته.

تضمين التغريدة
شكرا على المثال. أعطيتها أفضل تسديداتي. ربما فاتني شيء.

لا أعتقد أن هذا يعمل لأن الخاصية ترتبط بالحالة المحددة فيها ، لذلك يتم دائمًا إعادة بناء ExpensiveParent هنا. ثم أعتقد أن التخزين المؤقت للطفل يمثل أيضًا مشكلة ، كما في مثال Builder ، سيعرف فقط إعادة بناء الطفل عند بناء الحالة الأم ، ولكن في هذه الطريقة لا تعرف الخاصية متى تُبطل ذاكرة التخزين المؤقت الخاصة بها (ولكن ربما هذا هل يمكن حلها؟)

لكن tbh ، هذه هي حالة الاستخدام المثالية للبناة ، عندما تريد تقديم سياق جديد. أعتقد أنه من الرائع أن يكون لديك سياق StatefulProperties (الحالة الخالصة) و StatefulWidgets (مزيج من الحالة والتخطيط).

في أي وقت تقوم فيه بإنشاء سياق فرعي عمدًا ، ستقوم بذلك بحكم التعريف في أسفل شجرتك ، مما يساعد في مكافحة أحد العوائق الرئيسية للبناة (التعشيش القسري عبر الشجرة بأكملها)

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

ومع ذلك ، أعتقد أن عرض esDotDev و @ TimWhiting أعلاه مثير جدًا للاهتمام ويمكن أن يحل هذه المشكلات. إنها ليست مختصرة مثل الخطافات ، لكنها أكثر موثوقية. أعتقد أنه سيكون من المنطقي تمامًا تجميع شيء من هذا القبيل ، حتى أنه يمكن أن يكون مفضلًا في Flutter إذا كان يعمل بشكل جيد. لست متأكدًا من أنها منطقية كميزة أساسية لإطار العمل لأن التحسين ليس - هذا - جوهريًا بمجرد أن تأخذ في الاعتبار التعقيد حول بناء الخصائص وتأثير الأداء ، وكيف يفضل الأشخاص المختلفون الأنماط المختلفة. في نهاية اليوم ، من الجيد أن يستخدم أشخاص مختلفون أنماطًا مختلفة ، لكننا لا نريد أن يكون لإطار العمل الأساسي أنماط متعددة ، فهذا مجرد تضليل للمطورين الجدد.

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

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

تعمل الخطافات مع إعادة التحميل دون مشكلة.
يتسبب الخطاف الأول مع نوع وقت تشغيل غير متطابق في إتلاف كل الخطافات اللاحقة.

هذا يدعم الإضافة والحذف وإعادة الترتيب.

أعتقد أن هناك حجة مفادها أن التجريد الكامل أفضل من التجريد الجزئي الموجود حاليًا.

إذا أردت أن أفهم كيف يعمل Animator في سياق خاصية ما ، فإما أن أتجاهلها تمامًا ، أو أقفز إليها ، وكل شيء موجود هناك ، مستقل ومتماسك.

إذا أردت أن أفهم كيف يعمل AnimatorController في سياق StatefulWidget ، فأنا بحاجة (أنا مجبر) على فهم خطافات دورة الحياة الأساسية ، ولكن بعد ذلك أعفي من فهم كيفية عمل آلية التجزئة الأساسية. هذا هو أسوأ ما في العالمين بمعنى ما. ليس سحرًا كافيًا لجعله "يعمل فقط" ، ولكن يكفي فقط لإرباك المستخدمين الجدد وإجبارهم على الثقة بشكل أعمى في بعض mixin (وهو في حد ذاته مفهوم جديد لمعظم الناس) وخاصية سحرية مقابل المزامنة.

لست متأكدًا من الأمثلة الأخرى في قاعدة التعليمات البرمجية ، ولكن هذا ينطبق على أي موقف تم فيه توفير بعض الخلطات المساعدة لـ StatefulWidget ، ولكن لا يزال هناك بعض عمليات التمهيد الأخرى التي يجب إجراؤها دائمًا. سيتعلم Dev's bootstraping (الجزء الممل) ويتجاهل Mixin (الجزء المثير للاهتمام / المعقد)

ومع ذلك ، أعتقد أن عرض esDotDev و @ TimWhiting أعلاه مثير جدًا للاهتمام ويمكن أن يحل هذه المشكلات. إنها ليست مختصرة مثل الخطافات ، لكنها أكثر موثوقية

كيف يكون هذا أكثر موثوقية؟

ما زلنا لا نستطيع إنشاء / تحديث الخصائص بشروط أو خارج دورة حياتها حيث يمكننا الدخول في حالة سيئة. على سبيل المثال ، لن يؤدي استدعاء خاصية مشروطة إلى التخلص منها عندما يكون الشرط خاطئًا.
ولا يزال يتم إعادة تقييم جميع الممتلكات في كل إعادة بناء.

ولكنه يتسبب في العديد من المشكلات ، مثل إجبار المستخدمين على استخدام ! كل مكان بعد NNBD أو السماح للمستخدمين بالوصول إلى خاصية قبل تحديثها.

على سبيل المثال ، ماذا لو قرأ شخص ما خاصية داخل didUpdateWidget ؟

  • هل تم تنفيذ initProperties قبل دورة الحياة؟ ولكن هذا يعني أننا قد نضطر إلى تحديث الخصائص عدة مرات لكل بناء.
  • هل تم تنفيذ initProperties بعد didUpdateWidget؟ ثم قد يؤدي استخدام الخصائص الموجودة داخل didUpdateWidget إلى حالة قديمة

إذن في النهاية ، لدينا كل مشكلات الخطافات ولكن:

  • لا يمكننا استخدام الخصائص داخل `` StatelessWidget. لذا فإن قابلية قراءة StreamBuilder / ValueListenableBuilder / ... لا تزال تمثل مشكلة - والتي كانت الشغل الشاغل.
  • هناك العديد من حالات الحافة
  • من الصعب إنشاء خصائص مخصصة (لا يمكننا فقط استخراج مجموعة من الخصائص في وظيفة)
  • من الصعب تحسين عمليات إعادة البناء

في النهاية ، المثال المعطى لا يختلف في السلوك عن:

class Example extends StatelessWidget {
  <strong i="29">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value3 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    return Container(
     margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
     color: Colors.red.withOpacity(value1),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

لكن بناء الجملة هذا يدعم الكثير من الأشياء ، مثل:

العوائد المبكرة:

class Example extends StatelessWidget {
  <strong i="34">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    if (condition) {
      return Container();
    }

    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    ...
  }
}

والتي ستتخلص من value2 عندما يتحول condition إلى false

استخراج حزم البناة في دالة:

Widget build(context) {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return Text('$foo $bar');
}

يمكن تغييرها إلى:

Builder<String> LabelBuilder() builder* {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return '$foo $bar';
}

Widget build(context) {
  final label = keyword LabelBuilder();

  return Text(label);
}

تحسين عمليات إعادة البناء

لا تزال المعلمة child ممكنة:

Widget build(context) {
  final value = keyword StreamBuilder();

  return Builder(
    builder: (context, child) {
      final value2 = keyword TweenAnimationBuilder();
      final value = keyword ValueListenableBuilder();

      return Whatever(child: child);
    },
    child: ExpensiveChild()
  );
}

كجزء من اللغة ، يمكن أن نحصل على سكر نحوي لهذا:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword TweenAnimationBuilder();
      final value2 = keyword ValueListenableBuilder();

      return Text();
    },
  );
}

المكافأة: كميزة لغوية ، يتم دعم المكالمات الشرطية

كجزء من اللغة ، يمكننا دعم مثل هذا السيناريو:

Widget build(context) {
  String label;

  if (condition) {
    label = keyword LabelBuilder();
  } else {
    label = keyword AnotherBuilder();
  }

  final value2 = keyword WhateverBuilder();

  return ...
}

إنه ليس مفيدًا جدًا ، ولكنه مدعوم - نظرًا لأن بناء الجملة يتم تجميعه ، فإنه قادر على التمييز بين كل استخدام keyword خلال الاعتماد على البيانات الوصفية غير المتوفرة بطريقة أخرى.

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

class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: 1),
        duration: widget.duration1,
        builder: (_, value1, __) {
          return TweenAnimationBuilder<double>(
              tween: Tween(begin: 0, end: 1),
              duration: widget.duration2,
              builder: (_, value2, __) {
                return TweenAnimationBuilder<double>(
                    tween: Tween(begin: 0, end: 1),
                    duration: widget.duration3,
                    builder: (_, value3, __) {
                      return Container(
                        margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
                        color: Colors.red.withOpacity(value1),
                      );
                    });
              });
        });
  }
}

من الصعب جدًا (لعيني على الأقل) اكتشاف الكود المهم. أيضًا ، fwiw ، كان علي أن أبدأ من جديد مثل 3 مرات عند كتابة هذا ، حيث كنت مرتبكًا باستمرار بشأن أي شريحة تنتمي إلى أين ، وأين يجب أن تذهب أشباه المستعمرات وما إلى ذلك. فاصلة منقوطة واحدة خاطئة وكفالة dartfmt تسحق كل شيء تمامًا.

كيف يكون هذا أكثر موثوقية؟

هذا مثال ممتاز على لماذا يجب أن يكون هذا البرنامج المساعد الأساسي imo. معرفة المجال المطلوبة هنا هي _deep_. لدي معرفة البرمجة النصية لتنفيذ نظام تخزين مؤقت بسيط مثل هذا ، وليس لدي حتى معرفة قريبة بالمجال لمعرفة كل حالة حافة يمكن أن تحدث ، أو الحالات السيئة التي يمكننا الدخول فيها. بخلاف ريمي ، أعتقد أن هناك 4 مطورين في العالم خارج فريق Flutter يعرفون هذه الأشياء! (من الواضح أن المبالغة).

تعد مسألة دعم الأدوات عديمة الجنسية مسألة جيدة. من ناحية فهمت ذلك ، فإن StatefulWidgets مطولة بشكل غريب. من ناحية أخرى ، نحن هنا نتحدث حقًا عن الإسهاب الخالص. لا توجد أخطاء يمكن أن تحدث من الاضطرار إلى تحديد فئتين ، لا توجد طريقة يمكنك من خلالها العبث ، المترجم لا يسمح لك ، لا يوجد أبدًا أي شيء مثير للاهتمام أريد القيام به في StatelessWidget. لذلك أنا لا أعتبر أن هذه مشكلة كبيرة ... سيكون من الجيد بالتأكيد الحصول عليها ، لكنها آخر 5٪ من برنامج imo ، وليس شيئًا يتعطل فيه.

من ناحية أخرى ... أن بناء الجملة من remi مع دعم الكلمات الرئيسية جميل للغاية ومرنة / قوية بجنون. وإذا كان يمنحك دعم StatelessWidget مجانًا ، فهذا مجرد دعم إضافي 🔥

دعم StatelessWidget هو IMO صفقة كبيرة. اختياري ، لكن لا يزال رائعًا جدًا.

على الرغم من أنني أوافق على أنه ليس أمرًا بالغ الأهمية ، إلا أن الناس يتقاتلون بالفعل على استخدام الوظائف بدلاً من StatelessWidget.
إن مطالبة الأشخاص باستخدام StatefulWidget لاستخدام البناة (حيث من المحتمل أن يكون لدى معظم البناة مكافئ خاصية) لن يؤدي إلا إلى تعميق الصراع.

ليس ذلك فحسب ، بل في عالم يمكننا فيه إنشاء وظائف ذات ترتيب أعلى في لعبة dart (https://github.com/dart-lang/language/issues/418) ، يمكننا التخلص من الفئات تمامًا:

<strong i="9">@StatelessWidget</strong>
Widget Example(BuildContext context, {Key key, String param}) {
  final value = keyword StreamBuilder();

  return Text('$value');
}

ثم تستخدم على النحو التالي:

Widget build(context) {
  // BuildContext and Key are automatically injected
  return Example(param: 'hello');
}

هذا شيء مدعوم من قبل function_widget - وهو مولد كود حيث تكتب وظيفة وتقوم بإنشاء فئة لك - والتي تدعم أيضًا HookWidget .

يتمثل الاختلاف في أن الحصول على دعم للوظائف ذات الترتيب الأعلى في Dart من شأنه أن يزيل الحاجة إلى إنشاء التعليمات البرمجية لدعم بناء الجملة.

إنني أخمن ما الذي تعنيه @ Hixie بمصداقية أكثر ، هو أنها لا تعاني من ترتيب العمليات / المشكلة الشرطية التي تعاني منها الخطافات ، لأن هذا `` غير موثوق به '' للغاية من POV المعماري (على الرغم من أنني أدرك أنها قاعدة سهلة تعلم ولا تنتهك بمجرد تعلمه).

لكن لا اقتراحك مع الكلمة الرئيسية. أعتقد أن حالة الكلمات الرئيسية الجديدة قوية جدًا:

  • أكثر مرونة وقابلية للتكوين من التطعيم على الدولة
  • المزيد من بناء الجملة الناجح
  • يعمل في Stateless وهو خيار جيد جدًا

ما لا يعجبني في ذلك ، هو أننا قلقون بشأن تكلفة تعيين الخصائص على كائن بسيط عدة مرات / بناء ، ولكن بعد ذلك ندافع عن حل سيخلق بشكل أساسي مليون مستوى من السياق ومجموعة من تكلفة التخطيط. هل أنا سوء فهم؟

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

إنني أخمن ما الذي تعنيه @ Hixie بمصداقية أكثر ، هو أنها لا تعاني من ترتيب العمليات / المشكلة الشرطية التي تعاني منها الخطافات ، لأن هذا `` غير موثوق به '' للغاية من POV المعماري (على الرغم من أنني أدرك أنها قاعدة سهلة تعلم ولا تنتهك بمجرد تعلمه).

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

هذه ليست مشكلة

وبالمثل ، إذا كانت الأخطاء المخصصة محظورة ، فكما ذكرت سابقًا ، فإن الخاصية تعاني من نفس المشكلة بالضبط.
لا يمكننا أن نكتب بشكل معقول:

Property property;

<strong i="12">@override</strong>
void initProperties() {
  if (condition) {
    property = init(property, MyProperty());
  }
}

لأن تبديل condition من صواب إلى خطأ لن يؤدي إلى التخلص من الممتلكات.

لا يمكننا حقًا أن نسميها في حلقة أيضًا. إنه غير منطقي حقًا ، لأنه مهمة لمرة واحدة. ما هي حالة استخدام تشغيل الخاصية في حلقة؟

وحقيقة أنه يمكننا قراءة الخصائص بأي ترتيب تبدو خطيرة
على سبيل المثال يمكننا كتابة:

Property first;
Property second;

<strong i="20">@override</strong>
void initProperties() {
  // The state of first depends on second, but second is updated after first
  // So we could end up in a bad state, similar to how the build method of a Widget should depend
  // on the context.size
  first = init(property, MyProperty(second?.value));

  second = init(property, Whatever());
}
> class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
>   <strong i="5">@override</strong>
>   Widget build(BuildContext context) {
>     return TweenAnimationBuilder<double>(
>         tween: Tween(begin: 0, end: 1),
>         duration: widget.duration1,
>         builder: (_, value1, __) {
>           return TweenAnimationBuilder<double>(
>               tween: Tween(begin: 0, end: 1),
>               duration: widget.duration2,
>               builder: (_, value2, __) {
>                 return TweenAnimationBuilder<double>(
>                     tween: Tween(begin: 0, end: 1),
>                     duration: widget.duration3,
>                     builder: (_, value3, __) {
>                       return Container(
>                         margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
>                         color: Colors.red.withOpacity(value1),
>                       );
>                     });
>               });
>         });
>   }
> }

هذا مثال غريب. هل أنت متأكد من أن AnimatedContainer لا يمكنه فعل ذلك بالفعل؟

بالطبع. المثال هنا هو استخدام 3 رسوم متحركة في بعض الأدوات لعمل "X". تم تبسيط X عن قصد في المثال لتسليط الضوء على كمية المتداول.

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

النقطة المهمة هي أنه عند بناء شيء كهذا ، لا يعمل البناة بشكل رائع ، لأنهم يضعونك في فجوة في قابلية القراءة (وقابلية الكتابة) لتبدأ ، لذا فهم مناسبون تمامًا لحالات الاستخدام البسيطة ، ولا يفعلون "يؤلفون" حسنا. يؤلف كونه تكوين 2 أو أكثر من الأشياء.

لا تركز على كيفية استخدامها. في مثال حقيقي ، ...

... والعودة إلى المربع الأول. لماذا لا تقدم مثالا حقيقيا ؟

هل تحتاج إلى مثال حقيقي لاستخدام الرسوم المتحركة المعقدة؟
https://github.com/gskinnerTeam/flutter_vignettes

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

بالطبع. المثال هنا هو استخدام 3 رسوم متحركة في بعض الأدوات لعمل "X". تم تبسيط X عن قصد في المثال لتسليط الضوء على كمية المتداول.

القطعة "الأساسية" ستكون مائة سطر أو شيء من هذا القبيل

في منشور آخر قمت بنشر مثال مع نموذج معياري يحجب "النواة" ، لكن الآن تخبرنا أن اللب سيكون مئات الأسطر؟ إذن في الواقع ، سيكون النموذج المعياري ضئيلًا مقارنةً بالنواة؟ لا يمكن استخدام الطريقتين.
أنت تغير حججك باستمرار.

لا تركز على كيفية استخدامها. في مثال حقيقي ، ...

... والعودة إلى المربع الأول. لماذا لا تقدم مثالا حقيقيا ؟

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

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

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

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

في منشور آخر قمت بنشر مثال مع نموذج معياري يحجب "النواة" ، لكن الآن تخبرنا أن اللب سيكون مئات الأسطر؟ إذن في الواقع ، سيكون النموذج المعياري ضئيلًا مقارنةً بالنواة؟ لا يمكن استخدام الطريقتين.
أنت تغير حججك باستمرار.

مرة أخرى ، لا تتعلق هذه المشكلة بالنمط المعياري بل تتعلق بقابلية القراءة وقابلية إعادة الاستخدام.
لا يهم إذا كان لدينا 100 سطر.
ما يهم هو مدى قابلية قراءة هذه السطور / صيانتها / إعادة استخدامها.

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

هل تحتاج إلى مثال حقيقي لاستخدام الرسوم المتحركة المعقدة؟
https://github.com/gskinnerTeam/flutter_vignettes

بالتأكيد ، لا يمكنك أن تتوقع مني البحث في مشروعك بالكامل. أي ملف يجب أن أنظر إليه بالضبط؟

إن عرض بعض الرسوم المتحركة التعسفية المعقدة لن يفعل شيئًا سوى تشويش المثال.

العكس تماما. سيكون عرض بعض الرسوم المتحركة التعسفية المعقدة التي لا يمكن حلها بأي حل موجود هو المثال ، وهذا ما تطالب به Hixie باستمرار ، على ما أعتقد.

فقط امسح الصور المتحركة وابدأ في تخيل كيف يمكنك بناء بعض هذه الأشياء. هذا الريبو هو في الواقع 17 تطبيقًا مستقلًا. لا يمكنك أيضًا أن تتوقع مني أن أكتب لك بعض الرسوم المتحركة التعسفية فقط لأثبت لك أن الرسوم المتحركة المعقدة يمكن أن توجد. لقد كنت أقوم ببنائها لمدة 20 عامًا بدءًا من Flash ، كل واحدة مختلفة عن سابقتها. وهي ليست خاصة بالرسوم المتحركة على أي حال ، فهي فقط أبسط واجهة برمجة تطبيقات مألوفة لتوضيح نقطة أكبر.

كما تعلم كيف ، عندما تستخدم رسامًا متحركًا ، هناك 6 أشياء تحتاج إلى القيام بها في كل مرة ، ولكنها تحتاج أيضًا إلى خطافات لدورة الحياة ؟؟ حسنًا ، الآن قم بتوسيع ذلك إلى أي شيء يحتوي على 6 خطوات عليك القيام بها في كل مرة ... وتحتاج إلى استخدامه في مكانين. أو تحتاج إلى استخدام 3 منهم في وقت واحد. من الواضح أنها مشكلة في ظاهرها ، ولا أعرف ما الذي يمكنني إضافته لشرحها.

تدور البرمجة حول إنشاء التجريدات وأتمتة العمل ، فأنا لا أرى حقًا الهدف من إعادة نفس الشيء مرارًا وتكرارًا في فئات وملفات مختلفة.

__الجميع__؟ لذا فإن الأداء وقابلية الصيانة ليست ذات صلة؟

تأتي نقطة عندما تكون "أتمتة" العمل و "أداء" العمل متشابهين.

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

TimWhiting هل تمانع في وضع ملف ترخيص في https://github.com/TimWhiting/local_widget_state_approaches الريبو؟ بعض الأشخاص غير قادرين على المساهمة بدون ترخيص قابل للتطبيق (BSD أو MIT أو ما شابه ذلك بشكل مثالي).

لماذا يجب علي ، كمستخدم ، أن أتحمل مثل هذه النمذجة المعيارية في أي حال ، مع إعطاء طريقة مكافئة بدرجة كافية للتعبير عن الشيء نفسه؟

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

فقط امسح الصور المتحركة وابدأ في تخيل كيف يمكنك بناء بعض هذه الأشياء.

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

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

سيكون عرض بعض الرسوم المتحركة التعسفية المعقدة التي لا يمكن حلها بأي حل موجود هو المثال ، وهذا ما تطالب به Hixie باستمرار ، على ما أعتقد.

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

أي طلب لتقديم شيء غير ممكن اليوم هو خارج الموضوع.

TimWhiting هل تمانع في وضع ملف ترخيص في https://github.com/TimWhiting/local_widget_state_approaches الريبو؟ بعض الأشخاص غير قادرين على المساهمة بدون ترخيص قابل للتطبيق (BSD أو MIT أو ما شابه ذلك بشكل مثالي).

منتهي. عذرًا ، لم يكن لدي متسع من الوقت للعمل على الأمثلة ، لكن ربما سأصل إلى ذلك في وقت ما هذا الأسبوع.

سيكون عرض بعض الرسوم المتحركة التعسفية المعقدة التي لا يمكن حلها بأي حل موجود هو المثال ، وهذا ما تطالب به Hixie باستمرار ، على ما أعتقد.

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

أي طلب لتقديم شيء غير ممكن اليوم هو خارج الموضوع.

اسمحوا لي أن أعيد صياغة ما قلته.

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

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

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

TimWhiting هل تمانع في وضع ملف ترخيص في https://github.com/TimWhiting/local_widget_state_approaches الريبو؟ بعض الأشخاص غير قادرين على المساهمة بدون ترخيص قابل للتطبيق (BSD أو MIT أو ما شابه ذلك بشكل مثالي).

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

يتمثل الطلب أساسًا في تحويل هذا إلى شيء لا يمكن لـ AnimatedContainer معالجته:

Container(margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30), color: Colors.red.withOpacity(value1));

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

أعتقد أنني قدمت أمثلة قانونية ممتازة للمشكلة مع كل من البنائين وإعادة استخدام دولة الفانيليا:
https://github.com/flutter/flutter/issues/51752#issuecomment -671566814
https://github.com/flutter/flutter/issues/51752#issuecomment -671489384

عليك ببساطة أن تتخيل العديد من هذه الحالات (اختر السم الخاص بك) ، وانتشرت على نطاق واسع عبر مشروع يضم أكثر من 1000 ملف فئة ، وستحصل على صورة مثالية لقابلية القراءة وقضايا الصيانة التي نحاول تجنبها.

هل الصور النموذجية التي قدمها Rudiksz ؟ ما الذي ينقصهم؟ أفترض أنه لا توجد مقاييس أداء هناك ولكن rousselGit متأكد من أنها ليست أقل أداءً من البناة.

esDotDev أعتقد أن الهدف هو الحصول على مثال أساسي واحد يمكن من خلاله مقارنة جميع حلول إدارة دورة الحياة (ليس فقط الخطافات ولكن الآخرين في المستقبل أيضًا). إنه نفس مبدأ TodoMVC ، فلن تشير بالضرورة إلى العديد من التطبيقات الأخرى في React و Vue و Svelte وما إلى ذلك لإظهار الفرق بينهم ، فأنت تريد منهم جميعًا أن يقوموا بتنفيذ نفس التطبيق ، ثم يمكنك المقارنة.

هذا منطقي بالنسبة لي ، لكنني لا أفهم لماذا يجب أن يكون أكبر من صفحة واحدة.

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

بالتأكيد ، لا يحتوي المستودع من

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

Hixie طلب التطبيقات الحقيقية لمقارنة الأساليب معيب.

هناك نوعان من العيوب:

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

على سبيل المثال ، لا يمكننا كتابة تطبيق باستخدام:

final snapshot = keyword StreamBuilder();

لأن هذا لم يتم تنفيذه.

لا يمكننا الحكم على الأداء أيضًا ، لأن هذا يقارن بين رمز POC مقابل كود الإنتاج.

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

الحكم على أداء التصميمات ، وتقييم قابلية استخدام واجهة برمجة التطبيقات ، وتنفيذ الأشياء قبل أن يكون لدينا تطبيقات ... هذه كلها جزء من تصميم واجهة برمجة التطبيقات. مرحبا بك في عملي. :-) (Flutter trivia: هل تعلم أنه تم تنفيذ أول بضعة آلاف من الأسطر من RenderObject و RenderBox et al قبل إنشاء dart: ui؟)

هذا لا يغير حقيقة أنك تطلب المستحيل.

بعض المقترحات المقدمة هنا هي جزء من اللغة أو المحلل. من المستحيل على المجتمع تنفيذ ذلك.

لست متأكدًا من ذلك ، فالأطر واللغات الأخرى تقوم بتصميم واجهة برمجة التطبيقات طوال الوقت ، ولا أعتقد أن الأمر مختلف جدًا هنا ، أو أن Flutter لديه بعض الاختلافات أو الصعوبات الهائلة في تصميم واجهة برمجة التطبيقات مقارنة باللغات الأخرى. كما هو الحال في ، يفعلون ذلك دون الحاجة إلى دعم مترجم أو محلل ، فهم مجرد براهين على المفهوم.

لقد جمعت مثالاً لسيناريو الرسوم المتحركة "المعقد" الذي يستفيد بشكل جيد من 3 رسوم متحركة وهو محمّل إلى حد ما بالنماذج المعيارية والتشكيلات.

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

إذن فهذه سقالة بسيطة ، بها 3 ألواح تنزلق وتغلق. يستخدم 3 رسامين مع حالات منفصلة. في هذه الحالة ، لا أحتاج حقًا إلى التحكم الكامل في AnimatorController ، سيكون TweenAnimationBuilder جيدًا ، لكن التعشيش الناتج في شجرتى سيكون غير مرغوب فيه للغاية. لا يمكنني وضع علامة التبويب في أسفل الشجرة ، لأن اللوحات تعتمد على قيم بعضها البعض. AnimatedContainer ليس خيارًا هنا لأن كل لوحة تحتاج إلى الانزلاق خارج الشاشة ، فهي لا "تسحق".
https://i.imgur.com/BW6M3uM.gif
image

class _SlidingPanelViewState extends State<SlidingPanelView> with TickerProviderStateMixin {
  AnimationController leftMenuAnim;
  AnimationController btmMenuAnim;
  AnimationController rightMenuAnim;

  <strong i="12">@override</strong>
  void initState() {
    // Here I have to pass vsync to AnimationController, so I have to include a SingleTickerProviderMixin and somewhat magically pass 'this' as vsync.
    leftMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    btmMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    rightMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    // Here I have to call forward 3 times, cause there's no way to automate this common setup behavior
    leftMenuAnim.forward();
    btmMenuAnim.forward();
    rightMenuAnim.forward();
    // Here I have to manually bind to build, cause there is encapsulate this common setup behavior
    leftMenuAnim.addListener(() => setState(() {}));
    btmMenuAnim.addListener(() => setState(() {}));
    rightMenuAnim.addListener(() => setState(() {}));
    super.initState();
  }

  // Following 2 fxn are a blind spot as far as compiler is concerned.
  // Things may, or may not be implemented correctly, no warnings, no errors.
  <strong i="13">@override</strong>
  void dispose() {
    btmMenuAnim.dispose();
    leftMenuAnim.dispose();
    rightMenuAnim.dispose();
    super.dispose();
  }

  <strong i="14">@override</strong>
  void didUpdateWidget(SlidingPanelView oldWidget) {
    if (leftMenuAnim.duration != widget.slideDuration) {
      leftMenuAnim.duration = widget.slideDuration;
      btmMenuAnim.duration = widget.slideDuration;
      rightMenuAnim.duration = widget.slideDuration;
    }
    super.didUpdateWidget(oldWidget);
  }

  // End error-prone blind spot without a single line of unique code
  // ~50 lines in we can start to see some unique code

  void _toggleMenu(AnimationController anim) {
    bool isOpen = anim.status == AnimationStatus.forward || anim.value == 1;
    isOpen ? anim.reverse() : anim.forward();
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    double leftPanelSize = 320;
    double leftPanelPos = -leftPanelSize * (1 - leftMenuAnim.value);
    double rightPanelSize = 230;
    double rightPanelPos = -rightPanelSize * (1 - rightMenuAnim.value);
    double bottomPanelSize = 80;
    double bottomPanelPos = -bottomPanelSize * (1 - btmMenuAnim.value);
    return Stack(
      children: [
        //Bg
        Container(color: Colors.white),
        //Content Panel
        Positioned(
          top: 0,
          left: leftPanelPos + leftPanelSize,
          bottom: bottomPanelPos + bottomPanelSize,
          right: rightPanelPos + rightPanelSize,
          child: ChannelInfoView(),
        ),
        //Left Panel
        Positioned(
          top: 0,
          left: leftPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: leftPanelSize,
          child: ChannelMenu(),
        ),
        //Bottom Panel
        Positioned(
          left: 0,
          right: 0,
          bottom: bottomPanelPos,
          height: bottomPanelSize,
          child: NotificationsBar(),
        ),
        //Right Panel
        Positioned(
          top: 0,
          right: rightPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: rightPanelSize,
          child: SettingsMenu(),
        ),
        // Buttons
        Row(
          children: [
            Button("left", ()=>_toggleMenu(leftMenuAnim)),
            Button("btm", ()=>_toggleMenu(btmMenuAnim)),
            Button("right", ()=>_toggleMenu(rightMenuAnim)),
          ],
        )
      ],
    );
  }
}

//Demo helpers
Widget Button(String lbl, VoidCallback action) => FlatButton(child: Text(lbl), onPressed: action, color: Colors.grey);
Widget ChannelInfoView() => _buildPanel(Colors.red);
Widget ChannelMenu() => _buildPanel(Colors.pink);
Widget SettingsMenu() => _buildPanel(Colors.blue);
Widget NotificationsBar() => _buildPanel(Colors.grey);
Widget _buildPanel(Color c) => Container(color: c, child: Container(color: Colors.white.withOpacity(.5)), padding: EdgeInsets.all(10));

لذلك ، من بين 100 سطر أو نحو ذلك في ذلك الجسم ، فإن ما يقرب من 40٪ أو نحو ذلك عبارة عن لوحة مرجعية نقية. 15 سطرًا على وجه الخصوص ، حيث يمكن لأي شيء مفقود أو تمت كتابته بشكل خاطئ ، أن يتسبب في أخطاء يصعب اكتشافها.

إذا استخدمنا شيئًا مثل StatefulProperty ، فسيؤدي ذلك إلى تقليل النموذج المعياري إلى 15٪ أو نحو ذلك (يوفر حوالي 25 سطرًا). بشكل حاسم ، سيؤدي هذا إلى حل مشكلة الأخطاء الخادعة ومنطق العمل المكرر تمامًا ، لكنه لا يزال مطولًا بعض الشيء خاصةً لأنه يتطلب StatefulWidget ، وهو عبارة عن 10 أسطر يتم ضربها مباشرة من الأعلى.

إذا استخدمنا شيئًا مثل "الكلمات الرئيسية" ، فإننا نقلل الخطوط المعيارية إلى 0٪ أساسًا. يمكن أن يكون التركيز الكامل للفصل على منطق الأعمال (الفريد) وعناصر الشجرة المرئية. نحن نجعل استخدام StatefulWIdgets أكثر ندرة بشكل عام ، وتصبح الغالبية العظمى من المشاهدات أقل إسهابًا بنسبة 10 أو 20٪ وأكثر تركيزًا.

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

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

يعمل هذا بشكل عام بشكل جيد ، لكنه لا يزال الوقت والجهد ، والسبب الوحيد لوجوده حرفيًا هو أن استخدام الرسوم المتحركة صعب للغاية (وهو أمر موجود لأن إعادة استخدام المكونات ذات الحالة بشكل عام أمر صعب للغاية). في Unity أو AIR على سبيل المثال ، لن أقوم بإنشاء فئة مخصصة لتحريك لوحة على محور واحد فقط ، سيكون مجرد سطر واحد من التعليمات البرمجية لفتحه أو إغلاقه ، ولن يكون هناك شيء يمكن أن تفعله أداة مخصصة. في Flutter ، يتعين علينا إنشاء عنصر واجهة مستخدم مخصص ، لأنه حرفيًا الطريقة الوحيدة المعقولة لتغليف تمهيد وتمزيق AnimatorController (ما لم نرغب في التداخل والتداخل والتداخل باستخدام TAB)

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

للتأكد من اكتمالها ، إليك حالة الاستخدام نفسها ، المصنوعة من البناة. تم تقليل عدد الخطوط بشكل كبير ، ولا توجد فرصة للأخطاء ، ولا حاجة لتعلم المفهوم الأجنبي RE TickerProviderMixin ... ولكن هذا التداخل هو حزن ، والطريقة التي يتم بها رش المتغيرات المختلفة في جميع أنحاء الشجرة (قيم النهاية الديناميكية ، القيمة 1 ، القيمة 2 ، إلخ. ) يجعل قراءة منطق الأعمال أكثر صعوبة مما يجب.

"" دارت
يمتد class _SlidingPanelViewState إلى الحالة{
منطقي isLeftMenuOpen = صحيح ؛
منطقية isRightMenuOpen = صحيح ؛
منطقي isBtmMenuOpen = صحيح ؛

@تجاوز
بناء الأداة (سياق BuildContext) {
عودة TweenAnimationBuilder(
توين: Tween (البدء: 0 ، النهاية: isLeftMenuOpen؟ 1: 0) ،
المدة: widget.slide
باني: (_، leftAnimValue، __) {
عودة TweenAnimationBuilder(
توين: Tween (البدء: 0 ، النهاية: isRightMenuOpen؟ 1: 0) ،
المدة: widget.slide
باني: (_، rightAnimValue، __) {
عودة TweenAnimationBuilder(
توين: توين (البداية: 0 ، النهاية: isBtmMenuOpen؟ 1: 0) ،
المدة: widget.slide
باني: (_، btmAnimValue، __) {
ضعف leftPanelSize = 320 ؛
مزدوج leftPanelPos = -leftPanelSize * (1 - leftAnimValue) ؛
مزدوج rightPanelSize = 230 ؛
مزدوج rightPanelPos = -rightPanelSize * (1 - rightAnimValue) ؛
ضعف bottomPanelSize = 80 ؛
double bottomPanelPos = -BottomPanelSize * (1 - btmAnimValue) ؛
مكدس الإرجاع (
الأطفال: [
// Bg
الحاوية (اللون: ألوان. أبيض) ،
// مجال المحتوى الرئيسي
تم وضعه (
أعلى: 0 ،
اليسار: leftPanelPos + leftPanelSize ،
أسفل: bottomPanelPos + bottomPanelSize ،
يمين: rightPanelPos + rightPanelSize ،
الطفل: ChannelInfoView () ،
) ،
// اللوحة اليسرى
تم وضعه (
أعلى: 0 ،
اليسار: leftPanelPos ،
أسفل: bottomPanelPos + bottomPanelSize ،
العرض: leftPanelSize ،
الطفل: ChannelMenu () ،
) ،
//اللوحة السفلية
تم وضعه (
اليسار: 0 ،
اليمين: 0 ،
أسفل: bottomPanelPos ،
ارتفاع: bottomPanelSize ،
الطفل: NotificationsBar () ،
) ،
// اللوحة اليمنى
تم وضعه (
أعلى: 0 ،
اليمين: rightPanelPos ،
أسفل: bottomPanelPos + bottomPanelSize ،
العرض: rightPanelSize ،
child: SettingsMenu ()،
) ،
// أزرار
صف(
الأطفال: [
زر ("يسار" ، () => setState (() => isLeftMenuOpen =! isLeftMenuOpen)) ،
زر ("btm"، () => setState (() => isBtmMenuOpen =! isBtmMenuOpen)) ،
Button ("right"، () => setState (() => isRightMenuOpen =! isRightMenuOpen)) ،
] ،
)
] ،
) ؛
} ،
) ؛
} ،
) ؛
} ،
) ؛
}
}

هذا الأخير مثير للاهتمام ... كنت في الأصل أقترح أن يكون البناة حول الحاجيات الموضعية بدلاً من Stack (وما زلت أقترح ذلك للوحات اليمنى واليسرى) ولكن بعد ذلك أدركت أن الجزء السفلي يؤثر الثلاثة جميعًا ، وأدركت أن المنشئ يمنحك وسيطة واحدة child الواقع ليست كافية ، لأنك تريد حقًا الاحتفاظ بكل من Container Row وثابت

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

للتسجيل ، هذا ليس مصادفة. هذا كثيرًا حسب التصميم. إن وجود هذه الأدوات المصغّرة هي أدوات خاصة بها يؤدي إلى تحسين الأداء. كنا نهدف إلى أن يكون هذا هو كيفية استخدام الناس لـ Flutter. هذا ما كنت أشير إليه أعلاه عندما ذكرت "جزء كبير من فلسفة تصميم Flutter هو توجيه الناس نحو الاختيار الصحيح".

كنت في الأصل أقترح أن يكون البناة حول الحاجيات الموضعية بدلاً من Stack (وما زلت أقترح ذلك للوحات اليمنى واليسرى)

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

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

من المحتمل أن تكون حالة استخدام جيدة لجسم RenderObject مخصص.

أو 3 AnimatorObjects يمكن إدارتها بسهولة والتي يمكنها ربط نفسها بدورة حياة عنصر واجهة المستخدم: د

إن وجود هذه الأدوات المصغّرة هي أدوات خاصة بها يؤدي إلى تحسين الأداء.

نظرًا لأننا نحصل على ملموس هنا: في هذه الحالة نظرًا لأن هذه سقالة ، فإن كل عرض فرعي هو بالفعل عنصر واجهة مستخدم خاص به ، وهذا الرجل مسؤول فقط عن تخطيط الأطفال وترجمتهم. سيكون الطفل هو BottomMenu () ، ChannelMenu () ، SettingsView () ، MainContent () إلخ.

في هذه الحالة ، نقوم بلف مجموعة من الحاجيات القائمة بذاتها ، بطبقة أخرى من الحاجيات القائمة بذاتها ، ببساطة لإدارة النموذج المعياري حول تحريكها. لا أعتقد أن هذا فوز في الأداء؟ في هذه الحالة ، يتم دفعنا إلى ما يعتقده إطار العمل الذي نريد القيام به ، وليس ما نريد فعله بالفعل ، وهو كتابة وجهة نظر ذات أداء متساوٍ بطريقة أكثر نجاحًا وتماسكًا.

[تحرير] سوف أقوم بتحديث الأمثلة لإضافة هذا السياق

السبب الذي يجعلني أقترح RenderObject مخصصًا هو أنه سيجعل التخطيط أفضل. أوافق على أن جانب الرسوم المتحركة سيكون في عنصر واجهة مستخدم مصحوب بالحالة ، فسيتم فقط تمرير المضاعفات الثلاثة 0..1 (القيمة 1 ، والقيمة 2 ، والقيمة 3) إلى كائن التصيير بدلاً من الحصول على Stack math. لكن هذا في الغالب عرض جانبي لهذه المناقشة ؛ عند النقطة التي تقوم فيها بذلك ، لا تزال ترغب في القيام بالتبسيط الذي توفره الخطافات أو شيء مشابه في تلك القطعة ذات الحالة.

في ملاحظة أكثر صلة ، واجهت صعوبة في إنشاء عرض توضيحي لمشروع https://github.com/TimWhiting/local_widget_state_approaches/pull/1
أشعر بالفضول كيف ستبدو نسخة الخطافات. لست متأكدًا من أنه يمكنني رؤية طريقة جيدة لتبسيط الأمر ، لا سيما الحفاظ على خصائص الأداء (أو تحسينها ؛ هناك تعليق يظهر مكانًا يكون فيه حاليًا دون المستوى الأمثل).

(أنا أيضًا أشعر بالفضول الشديد إذا كان هذا هو نوع الأشياء التي إذا وجدنا طريقة لتبسيطها ، فسننتهي هنا ، أو إذا كانت تفتقد إلى أشياء مهمة سنحتاج إلى حلها قبل أن ننتهي.)

هل عناصر الاستعادة مهمة في هذا المثال؟ أواجه صعوبة في المتابعة بسبب ذلك ، ولا أعرف حقًا ما يفعله RestorationMixin. أفترض أن الأمر ... سيستغرق بعض الوقت لفهم هذا بالنسبة لي. أنا متأكد من أن ريمي سيخرج إصدار الخطافات في 4 ثوانٍ :)

استخدام واجهة برمجة تطبيقات الاستعادة باستخدام HookWidget بدلاً من StatefulHookWidget غير مدعوم في الوقت الحالي.

من الناحية المثالية ينبغي أن نكون قادرين على التغيير

final value = useState(42);

إلى:

final value = useRestorableInt(42);

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

كملاحظة جانبية ، تأتي React hooks مع ميزة "رئيسية" ، تُستخدم عادةً على النحو التالي:

int userId;

Future<User> user = useMemo(() => fetchUser(id), [id]);

حيث يعني هذا الرمز "تخزين نتيجة رد الاتصال مؤقتًا ، وإعادة تقييم رد الاتصال كلما تغير أي شيء داخل المصفوفة"

لقد قام Flutter_hooks بإعادة تطبيق 1to1 (لأنه مجرد منفذ) ، لكنني لا أعتقد أن هذا ما نريد القيام به من أجل رمز Flutter المحسّن.

ربما نريد:

int userId;

Future<User> user = useMemo1(id, (id) => fetchUser(id));

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

إنه ليس حرجًا في هذه المرحلة ، ولكن من الجدير بالذكر إذا كنا نخطط لاستخدام flutter_hooks للحصول على أمثلة.

Hixie لقد

لقد كان مثالًا مثيرًا للاهتمام ، مجد للتفكير فيه!
إنه مثال جيد على ذلك ، افتراضيًا ، يتم تنفيذ "نشطة" و "مدة" في كل مكان (وتعتمد على بعضها البعض في ذلك).
إلى هذه النقطة كان هناك العديد من مكالمات "if (active)" / "controller.repeat"

بينما مع الخطافات ، يتم التعامل مع كل المنطق بشكل تصريحي وتركيزه في مكان واحد ، بدون تكرار.

يوضح المثال أيضًا كيف يمكن استخدام الخطافات للتخزين المؤقت للكائنات بسهولة - مما أدى إلى إصلاح مشكلة إعادة إنشاء ExpensiveWidget كثيرًا.
نحصل على فوائد منشئات const ، لكنها تعمل مع المعلمات الديناميكية.

نحصل أيضًا على إعادة تحميل أفضل على الساخن. يمكننا تغيير المدة الزمنية Timer.periodic للون الخلفية ورؤية التغييرات السارية على الفور.

rousselGit هل لديك رابط؟ لم أر أي شيء جديد في مستودع TimWhiting .

استخدام واجهة برمجة تطبيقات الاستعادة باستخدام HookWidget بدلاً من StatefulHookWidget غير مدعوم في الوقت الحالي.

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

https://github.com/TimWhiting/local_widget_state_approaches/pull/3

متفق عليه ، لكنني لست قلقًا بشأن ذلك. لا يتطلب useAnimationController من المستخدمين الاهتمام بـ SingleTickerProvider على سبيل المثال.

يمكن أن يستفيد AutomaritKeepAlive من نفس المعاملة.
أحد الأشياء التي كنت أفكر فيها هو الحصول على خطاف "useKeepAlive (bool)"

هذا يتجنب كلا من mixin و "super.build (السياق)" (الأخير مربك للغاية)

نقطة أخرى مثيرة للاهتمام هي التغييرات المطلوبة أثناء إعادة البناء.

على سبيل المثال ، يمكننا مقارنة الفرق بين التغييرات اللازمة لتنفيذ TickerMode للنهج الخام مقابل الخطافات:

تختلط بعض الأشياء الأخرى في الفرق ، لكن يمكننا أن نرى منها ما يلي:

  • StatefulWidget مطلوب لنقل المنطق إلى دورة حياة مختلفة تمامًا
  • تغييرات الخطافات هي مواد مضافة بحتة. لم يتم تحرير / نقل الأسطر الموجودة.

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

متفق!
هذا أيضًا يجعل مراجعات الكود أسهل كثيرًا في القراءة:

final value = useSomething();
+ final value2 = useSomethingElse();

return Container(
  color: value.color,
-  child: Text('${value.name}'),
+  child: Text('${value.name} $value2'),
);

ضد:

return SomethingBuilder(
  builder: (context, value) {
-    return Container(
-      color: value.color,
-      child: Text('$value'),
+    return SomethingElseBuilder(
+      builder: (context, value2) {
+        return Container(
+          color: value.color,
+          child: Text('${value.name} $value2'),
+        );
+      }
    );
  },
);

ليس من الواضح في المقارنة الثانية أن Container لم يتغير وأنه تم تغيير Text

rousselGit هل تعتقد أنه من المنطقي محاولة عمل إصدار يمثل كيف يمكن أن يبدو مع دعم الكلمات الرئيسية؟ هل هو مشابه إلى حد كبير للخطافات مع مجرد كلمة رئيسية مخصصة "للاستخدام" ، أم أنه أصبح من الأسهل متابعته؟ من الصعب مقارنة نهج الخطاف على قدم المساواة لأنه يحتوي على العديد من المفاهيم الأجنبية مثل useEffect و useMemo وما إلى ذلك والتي أعتقد أنها تجعلها تبدو أكثر سحرية مما هي عليه؟

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

في نهج الدولة ، نحن مضطرون إلى

  • نسخ ولصق المنطق (غير قابل للصيانة)
  • باستخدام منشئ (غير سهل القراءة ، خاصة عند استخدام التداخل)
  • الاختلاط (لا يؤلف جيدًا ، من السهل جدًا أن تتعارض الخلطات المختلفة في حالتها المشتركة)

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

أنا ، بأي حال من الأحوال ، مطور خبير. لكن هذا الأسلوب الجديد في إعادة استخدام المنطق وكتابته في مكان واحد مفيد حقًا.

لم أستخدم Vue.js منذ وقت طويل ، لكن لديهم أيضًا واجهة برمجة تطبيقات خاصة بهم (مستوحاة من الخطافات) لإصدارهم التالي ، قد يكون من المفيد إلقاء نظرة.

و CMIIW ، أعتقد أن قواعد خطافات التفاعل (لا تستخدم الشرطي) ، لا تنطبق مع Composition API. لذلك ، لا تحتاج إلى استخدام linter لفرض القواعد.

مرة أخرى ، يعزز قسم التحفيز بقوة البروتوكول الاختياري هنا:

التحفيز
يصبح التفكير في رمز المكونات المعقدة أكثر صعوبة مع نمو الميزات بمرور الوقت. يحدث هذا بشكل خاص عندما يقرأ المطورون كودًا لم يكتبوه بأنفسهم.
[كان هناك] عدم وجود آلية نظيفة وخالية من التكلفة لاستخراج وإعادة استخدام المنطق بين مكونات متعددة.

الكلمات الرئيسية هناك هي "نظيفة" و "خالية من التكلفة". الخلطات خالية من التكلفة ولكنها ليست نظيفة. البناة نظيفون ، لكنهم ليسوا خاليين من التكلفة من حيث قابلية القراءة ، ولا بالمعنى المعماري لعناصر واجهة المستخدم حيث يصعب عليهم التحرك حول الشجرة والعقل من حيث التسلسل الهرمي.

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

بعض الأقسام الأخرى ذات الصلة بشكل خاص.

لماذا لا يكفي مجرد وجود مكونات:

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

لماذا يعتبر تقليل التجزئة المنطقية وتغليف الأشياء بقوة أكبر هو الفوز:

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

أشعر بالفضول لمعرفة ما إذا كانت هناك مقترحات أخرى لأمثلة أساسية نحاول تحسينها بخلاف تلك التي قدمتها.
إذا كانت هذه هي نقطة البداية التي يريد الناس استخدامها ، فهذا رائع. ومع ذلك ، أود أن أقول إن أكبر مشكلة في الوقت الحالي هي الإسهاب ؛ ليس هناك الكثير من التعليمات البرمجية لإعادة استخدامها في هذا المثال. لذلك ليس من الواضح بالنسبة لي ما إذا كان يمثل تمثيلًا جيدًا للمشكلة كما هو موضح في OP.

لقد كنت أفكر أكثر في كيفية التعبير عن الخصائص التي سأبحث عنها شخصيًا في الحل ، وقد جعلني ذلك أدرك إحدى المشكلات الكبيرة التي أراها مع اقتراح الخطافات الحالي ، وهذا أحد أسباب عدم رغبت في ذلك. تريد دمجه في إطار عمل Flutter: Locality and Encapsulation ، أو بالأحرى عدم وجودهما. يستخدم تصميم الخطافات الحالة العامة (على سبيل المثال ، الثابت لتتبع الأداة التي يتم بناؤها حاليًا). IMHO هذه خاصية تصميم يجب أن نتجنبها. بشكل عام ، نحاول أن نجعل واجهات برمجة التطبيقات مستقلة بذاتها ، لذلك إذا استدعت دالة بمعامل واحد ، فيجب أن تكون واثقًا من أنها لن تكون قادرة على فعل أي شيء بقيم خارج تلك المعلمة (وهذا هو سبب قيامنا بتمرير BuildContext حولها) ، بدلاً من الحصول على ما يعادل useContext ). أنا لا أقول أن هذه خاصية يريدها الجميع بالضرورة ؛ وبالطبع يمكن للأشخاص استخدام الخطافات إذا لم تكن هذه مشكلة بالنسبة لهم. هذا شيء أود تجنبه في Flutter. في كل مرة كان لدينا حالة عالمية (على سبيل المثال في الروابط) انتهى بنا الأمر بالندم عليها.

من المحتمل أن تكون الخطافات عبارة عن طرق على السياق (أعتقد أنها كانت في الإصدار الأول) ، لكن بصراحة ، لا أرى قيمة كبيرة في دمجها كما هي حاليًا. قد يحتاج الدمج إلى بعض المزايا لفصل الحزمة ، مثل زيادة الأداء أو ربط أدوات تصحيح أخطاء محددة. وإلا فإنها ستضيف فقط إلى الارتباك ، على سبيل المثال سيكون لديك 3 طرق رسمية للاستماع إلى الاستماع: AnimatedBuilder و StatefulWidget & useListenable.

بالنسبة لي ، الطريق الذي يجب أن أقطعه هو تحسين إنشاء الكود - لقد اقترحت بعض التغييرات: https://github.com/flutter/flutter/issues/63323

إذا تم تنفيذ هذه الاقتراحات بالفعل ، فإن الأشخاص الذين يريدون حلولًا سحرية تشبه SwiftUI في تطبيقاتهم يمكنهم فقط إنشاء حزمة دون إزعاج أي شخص آخر.

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

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

يمكن أن تأخذ هذه الميزة مسارًا مختلفًا تمامًا عن الخطافات ، مثل ما تفعله SwiftUI أو Jetpack Compose ؛ اقتراح "mixin المسمى" ، أو اقتراح بناء الجملة للبناة.

أصر على حقيقة أن هذه المشكلة في جوهرها تتطلب تبسيط أنماط مثل StreamBuilder:

  • StreamBuilder لديه قابلية قراءة / كتابة سيئة بسبب تداخله
  • المزيجات والوظائف ليست بديلاً محتملاً لـ StreamBuilder
  • نسخ ولصق تطبيق StreamBuilder في جميع StatefulWidgets في جميع أنحاء غير معقول

ذكرت جميع التعليقات حتى الآن بدائل StreamBuilder ، لكل من السلوكيات المختلفة (إنشاء كائن يمكن التخلص منه ، أو تقديم طلبات HTTP ، ...) أو اقتراح صيغ مختلفة.

لست متأكدًا مما يمكن قوله أيضًا ، لذلك لا أرى كيف يمكننا التقدم أكثر.
ما هذا الذي لا تفهمه / لا توافق عليه في هذا البيان @ Hixie؟

rousselGit هل يمكنك إنشاء تطبيق تجريبي يوضح ذلك؟ حاولت إنشاء تطبيق تجريبي أظهر ما فهمت أنه المشكلة ، لكن يبدو أنني لم أفهم ذلك بالشكل الصحيح. (لست متأكدًا من الفرق بين "أنماط مثل StreamBuilder" وما فعلته في التطبيق التجريبي.)

  • StreamBuilder لديه قابلية قراءة / كتابة سيئة بسبب تداخله

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

نسخ ولصق تطبيق StreamBuilder في جميع StatefulWidgets في جميع أنحاء غير معقول

تقصد نسخ ولصق سطور التعليمات البرمجية التي تنشئ وتتخلص من التدفقات التي تحتاج StatefulWidgets إلى إنشائها والتخلص منها؟ نعم ، هذا معقول تمامًا.

إذا كان لديك 10 أو لا سمح الله بمئات _different_ Custom StatefulWidgets التي تحتاج إلى إنشاء / التخلص من التدفقات الخاصة بها - "استخدام" في مصطلحات الخطافات - ، لديك مشاكل أكبر تقلق بشأنها من إعادة استخدام "المنطق" أو التداخل. سأقلق حول سبب وجوب إنشاء تطبيقي للعديد من التدفقات المختلفة في المقام الأول.

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

لكي نكون منصفين ، أعتقد أنه من الجيد أن يعتقد شخص ما أن نمطًا معينًا غير معقول في تطبيقه.

أنا 100٪ في نفس الصفحة مثلك.
بالتأكيد ، يمكن للناس أن يفعلوا ما يريدون في تطبيقاتهم. ما أحاول الوصول إليه هو أن جميع المشكلات والصعوبات الموصوفة في هذا الموضوع ناتجة عن عادات البرمجة السيئة ، وفي الواقع ليس لها علاقة تذكر بـ Dart أو Flutter. هذا يشبه ، فقط رأيي ، لكنني أقول إذا كان أي شخص يكتب تطبيقًا ينشئ عشرات التدفقات في جميع أنحاء المكان ، ربما ينبغي عليه مراجعة تصميم التطبيق قبل أن يطلب "تحسين" إطار العمل.

على سبيل المثال ، تنفيذ الخطاف الذي جعله في مثال الريبو.

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final value = useAnimation(animation);

    final screenHeight = MediaQuery.of(context).size.height;
    final textHeight =
        useMemoized(() => math.sqrt(screenHeight), [screenHeight]);

    return Text(
      'Change Duration',
      style: TextStyle(fontSize: 10.0 + value * textHeight),
    );
  }

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

ما هو أسوأ من ذلك ، أن الخطافين "الجديد" و "القديم" لهما نفس نوع المثيل ، وكلاهما لهما نفس القيم ، ومع ذلك تتكرر الوظيفة على القيم للتحقق مما إذا كانت قد تغيرت. في _ كل علامة متحركة واحدة_.

هذا هو الناتج الذي أحصل عليه ، إلى ما لا نهاية.

/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true
I/flutter (28121): 3: --------------
I/flutter (28121): checking if the listenable did change
I/flutter (28121): Did the listenable change?false
I/flutter (28121): Use hook:_MemoizedHook<double>
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook1 keys:[1232.0]
I/flutter (28121): Hook2 :Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook2 keys:[1232.0]
I/flutter (28121): iterating over the hooks keys
I/flutter (28121): 2. Shoud we preserve the  state of _MemoizedHook<double>:true
I/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true

كل هذا العمل يتم على كل علامة متحركة واحدة. إذا أضفت خطافًا آخر مثل "اللون النهائي = استخدام الرسوم المتحركة(animationColor)؛ "، لتحريك اللون أيضًا ، الآن تتحقق الأداة مرتين إذا تم تحديثها.

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

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

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

Hixie أنت لم تجب على السؤال. ما هو الشيء الذي لا تفهمه / توافق عليه في القائمة النقطية المذكورة أعلاه؟

لا يمكنني أن أصنع مثالاً دون أن أعرف ما تريد مني أن أبينه.

Rudiksz بالتأكيد ، أي حل نعتبره فعليًا يحتاج إلى تعريفه أنشأت بها التطبيق التجريبي الذي قدمته إلى

rousselGit @ لم أرغب حقًا في الدخول في هذه الحشائش لأنه شخصي جدًا ولكن بما أنك تسأل:

  • أولاً ، سأتجنب استخدام التدفقات بشكل عام. ValueListenable هو نمط IMHO أفضل بكثير لحالات الاستخدام نفسها تقريبًا أو أكثر.
  • لا أعتقد أن StreamBuilder يصعب قراءته أو كتابته ؛ ولكن ، كما علق satvikpendem سابقًا ، فإن تاريخي عبارة عن أشجار متداخلة بعمق في HTML ، وقد كنت أحدق في Flutter Tree منذ 4 سنوات حتى الآن (لقد قلت من قبل أن الكفاءة الأساسية لـ Flutter هي كيفية السير على الأشجار العملاقة بكفاءة) ، لذلك من المحتمل أن يكون لدي تسامح أعلى من معظم الناس ، ورأيي هنا ليس مناسبًا حقًا.
  • فيما يتعلق بما إذا كانت المزيجات والوظائف قد تكون بديلاً محتملاً لـ StreamBuilder ، أعتقد أن الخطاف يوضح جيدًا أنه يمكنك بالتأكيد استخدام الوظائف للاستماع إلى التدفقات ، ومن الواضح أن المزيجات يمكنها فعل أي شيء يمكن للفئات القيام به هنا حتى لا أرى سبب عدم قيامها بذلك. سيكون حلا أيضا.
  • أخيرًا ، فيما يتعلق بتطبيقات لصق النسخ ، هذه مسألة ذاتية. أنا لا أنسخ المنطق وألصقه شخصيًا في initState / didUpdateWidget / dispose / build ، أكتبه مجددًا في كل مرة ، ويبدو الأمر جيدًا في الغالب. عندما يخرج عن السيطرة ، أقوم بإخراجه إلى عنصر واجهة مستخدم مثل StreamBuilder. لذا مرة أخرى ربما لا يكون رأيي ذا صلة هنا.

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

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

المسألة هي أنك تسأل أمثلة عن شيء يقع في مجال "واضح" بالنسبة لي.

لا أمانع في تقديم بعض الأمثلة ، لكن ليس لدي أي فكرة عما تتوقعه ، لأنني لا أفهم ما لا تفهمه.

لقد قلت بالفعل كل ما قلته.
الشيء الوحيد الذي يمكنني فعله دون فهم ما لا تفهمه هو أن أكرر نفسي.

يمكنني جعل بعض المقتطفات تعمل ، لكن هذا يعادل تكرار نفسي.
إذا لم يكن المقتطف مفيدًا ، فأنا لا أرى سبب تغيير القدرة على تشغيله لأي شيء.

فيما يتعلق بما إذا كانت المزيجات والوظائف قد تكون بديلاً محتملاً لـ StreamBuilder ، أعتقد أن الخطاف يوضح جيدًا أنه يمكنك بالتأكيد استخدام الوظائف للاستماع إلى التدفقات ، ومن الواضح أن المزيجات يمكنها فعل أي شيء يمكن للفئات القيام به هنا حتى لا أرى سبب عدم قيامها بذلك. سيكون حلا أيضا.

لا ينبغي اعتبار الخطافات كوظائف.
إنها لغة جديدة تشبه لغة التكرار / الدفق

لا تستطيع الوظائف أن تفعل ما تفعله الخطافات - ليس لديها حالة أو القدرة على إعادة بناء الحاجيات.

تم توضيح مشكلة mixins في OP. TL ؛ DR: الاسم يتعارض مع المتغيرات ومن المستحيل إعادة استخدام نفس المزيج عدة مرات.

rousselGit حسنًا ، نظرًا لأنك لا تمانع في تقديم بعض الأمثلة ، وبما أن الأمثلة التي

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

لا أستطيع التفكير في أي شيء لأضيفه إلى هذه الأمثلة.

لكن FWIW أعمل على تطبيق طقس مفتوح المصدر باستخدام Riverpod. سأربطها هنا عند الانتهاء.


لقد أجريت استطلاعًا على تويتر لطرح بعض الأسئلة حول البناة المتعلقة بالمشكلات التي تمت مناقشتها هنا:

https://twitter.com/remi_rousselet/status/1295453683640078336

لا يزال الاستطلاع معلقًا ، ولكن ها هي الأرقام الحالية:

Screenshot 2020-08-18 at 07 01 44

حقيقة أن 86٪ من 200 شخص يرغبون في طريقة لكتابة بناة لا تتضمن التعشيش تتحدث عن نفسها.

لكي أكون واضحًا ، لم أقترح أبدًا أنه لا ينبغي لنا معالجة هذه المشكلة. إذا اعتقدت أنه لا ينبغي لنا معالجتها ، فسيتم إغلاق المشكلة.

أعتقد أنني سأحاول تقديم مثال يستخدم المقتطفات التي ربطتها.

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

على سبيل المثال ، إليك خلاصة حول العديد من ValueListenableBuilder + TweenAnimationBuilder https://gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

على سبيل المثال ، إليك خلاصة حول العديد من ValueListenableBuilder + TweenAnimationBuilder https://gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

FWIW ، يمكن تنفيذ هذا المثال المعين بسهولة أكبر في mobx.
إنه في الواقع أقصر من تنفيذ الخطافات.

ما يمكن ملاحظته من Mobx هو ValueNotifiers على المنشطات وأداة Observer الخاصة به هي تطور ValueListenableBuilder من Flutter - يمكنه الاستماع إلى أكثر من ValueNotifier.
كونك بديلًا سريعًا لمجموعة ValueNotifier / ValueListenableBuilder ، فهذا يعني أنك ما زلت تكتب كود Flutter الاصطلاحي ، وهو في الواقع عامل مهم.

نظرًا لأنه لا يزال يستخدم Flutter's المدمج في Tween builder ، فلا داعي لتعلم / تنفيذ أدوات / خطافات جديدة (بمعنى آخر ، لا يحتاج إلى ميزات جديدة) ولا يحتوي على أي من نتائج أداء الخطافات.

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'counters.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home1(),
    );
  }
}

var counters = Counters();

class Home1 extends StatelessWidget {
  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(end: counters.firstCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.firstCounter += 100,
              child: Text('+'),
            ),
            // Both counters have voluntarily a different Curve and duration
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 2),
                curve: Curves.easeInOut,
                tween: IntTween(end: counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.secondCounter += 100,
              child: Text('+'),
            ),
            const Text('total:'),
            // The total must take into account the animation of both counters
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(
                    end: counters.firstCounter + counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

العدادات. ابدأ بكونها بسيطة مثل ...

part 'counters.g.dart';

class Counters = _Counters with _$Counters;
abstract class _Counters with Store {
  <strong i="18">@observable</strong>
  int firstCounter = 0;

  <strong i="19">@observable</strong>
  int secondCounter = 0;
}

إليك تطبيق آخر لا يحتاج حتى إلى منشئي الرسوم المتحركة. طريقة بناء عنصر واجهة المستخدم نقية بقدر ما يمكن أن تكون ، تقريبًا مثل ملف html الدلالي ... مثل القالب.

https://gist.github.com/Rudiksz/cede1a5fe88e992b158ee3bf15858bd9

Rudiksz سلوك الحقل "الإجمالي" معطل في
وبالمثل ، لست متأكدًا مما يضيفه هذا المثال إلى المتغير ValueListenableBuilder .

بالنسبة إلى جوهرك الأخير ، تم كسر TickerProvider لأنه لا يدعم TickerMode - ولا تتم إزالة المستمعين أو التخلص من وحدات التحكم.

ومن المحتمل أن يكون Mobx خارج الموضوع. نحن لا نناقش كيفية تنفيذ الحالة المحيطة / ValueListenable مقابل المتاجر مقابل التدفقات ، ولكن بدلاً من ذلك نناقش كيفية التعامل مع بناة محليين / متداخلين - وهو ما لا يحله Mobx بأي شكل من الأشكال

- –––

أيضًا ، ضع في اعتبارك أنه في مثال الخطافات ، يمكن / يجب استخراج useAnimatedInt في حزمة وأنه لا يوجد تكرار للمدة / المنحنى بين الرسوم المتحركة النصية الفردية والإجمالي.

بالنسبة للعروض ، مع الخطافات ، نقوم بإعادة بناء عنصر واحد فقط ، بينما مع البناة الذين يمتلكون ، يعيدون بناء 2-4 بناة.
لذلك يمكن أن تكون الخطافات أسرع.

سلوك الحقل "الإجمالي" معطل في مقتطفك. لا يتطابق مع المثال ، حيث قد يتحرك كلا العددين معًا ولكنهما ينهيان التحريك في أوقات مختلفة ومنحنيات مختلفة.

من الواضح أنك لم تحاول حتى إدارة المثال. يتصرف تمامًا مثل التعليمات البرمجية الخاصة بك.

بالنسبة إلى جوهرك الأخير ، فإن TickerProvider معطل لأنه لا يدعم TickerMode.

لا أعلم ماذا تقصد بهذا. قمت بإعادة هيكلة مثالك ، والذي لا يستخدم TickerMode. أنت مرة أخرى تقوم بتغيير المتطلبات.

بالنسبة للعروض ، مع الخطافات ، نقوم بإعادة بناء عنصر واحد فقط ، بينما مع البناة الذين يمتلكون ، يعيدون بناء 2-4 بناة. لذلك يمكن أن تكون الخطافات أسرع.

لا تعني لا. تقوم أدوات الخطاف بالاستقصاء باستمرار عن التغييرات في كل بناء. البناة الذين يعتمدون على العناصر القيمة "متفاعلون".

وبالمثل ، لست متأكدًا مما يضيفه هذا المثال إلى متغير ValueListenableBuilder .

ومن المحتمل أن يكون Mobx خارج الموضوع. نحن لا نناقش كيفية تنفيذ الحالة المحيطة / ValueListenable مقابل المتاجر مقابل التدفقات ، ولكن بالأحرى كيفية التعامل مع بناة محليين / متداخلين - وهو ما لا يحله Mobx بأي شكل من الأشكال

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

من الواضح أنك لم تحاول حتى إدارة المثال. يتصرف تمامًا مثل التعليمات البرمجية الخاصة بك.

لا. يتحرك العداد الأول خلال 5 ثوانٍ ، والثاني خلال ثانيتين - ويستخدم كلاهما منحنى مختلفًا أيضًا.

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

من ناحية أخرى ، لا يأخذ التنفيذ في الاعتبار هذه الحالة ، لأنه دمج 2 TweenAnimationBuilders في واحد.
لإصلاحها ، سيتعين علينا كتابة:

Observer(
  builder: (context) {
    return TweenAnimationBuilder<int>(
      duration: const Duration(seconds: 5),
      curve: Curves.easeOut,
      tween: IntTween(end: counters.firstCounter),
      builder: (context) {
        return Observer(
          valueListenable: secondCounter,
          builder: (context, value2, child) {
            return TweenAnimationBuilder<int>(
              duration: const Duration(seconds: 2),
              curve: Curves.easeInOut,
              tween: IntTween(end: counters.secondCounter),
              builder: (context, value2, child) {
                return Text('${value + value2}');
              },
            );
          },
        );
      },
    );
  },
)

يعتبر كل من TweenAnimationBuilders ضروريًا لاحترام حقيقة أن كلا العدادات يمكن تحريكها بشكل فردي. و Observer ضروريان لأن أول Observer لا يمكن أن يلاحظ counters.secondCounter


لا تعني لا. تقوم أدوات الخطاف بالاستقصاء باستمرار عن التغييرات في كل بناء. البناة الذين يعتمدون على العناصر القيمة "متفاعلون".

أنت تتجاهل ما يفعله Element ، والذي يحدث أنه نفس الشيء مما تفعله الخطافات: مقارنة وقت التشغيل والمفاتيح ، وتحديد ما إذا كنت تريد إنشاء عنصر جديد أو تحديث العنصر الحالي

أخذت مثالك و "تعاملت" مع ValueListenableBuilders المتداخلة * والبناة الصغار

بافتراض أنه تم إصلاح مشكلة العدد الإجمالي ، ما التداخل الذي تمت إزالته؟

Observer(
  builder: (context) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: counters.firstCounter),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

لا يختلف عن:

ValueListenableBuilder<int>(
  valueListenable: firstCounter,
  builder: (context, value, child) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: value),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

من حيث التعشيش.

إذا كنت تشير إلى جوهرك ، فكما ذكرت من قبل ، فإن هذا الأسلوب يكسر TickerProvider / TickerMode . يجب الحصول على vsync باستخدام SingleTickerProviderClientStateMixin وإلا فإنه لا يدعم منطق الكتم ، والذي يمكن أن يسبب مشاكل في الأداء.
لقد أوضحت هذا في مقال خاص بي: https://dash-overflow.net/articles/why_vsync/

ومع هذا النهج ، يتعين علينا إعادة تنفيذ منطق توين في كل موقع يريد في الأصل إنشاء TweenAnimationBuilder. هذا يؤدي إلى تكرار كبير ، خاصة إذا أخذنا في الاعتبار أن المنطق ليس تافهًا

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

من ناحية أخرى ، لا يأخذ التنفيذ في الاعتبار هذه الحالة ، لأنه دمج 2 TweenAnimationBuilders في واحد.

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

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

إذا كنت تشير إلى موضوعك ، فكما ذكرت من قبل ، فإن هذا النهج يكسر TickerProvider / TickerMode. يجب الحصول على VSync باستخدام SingleTickerProviderClientStateMixin وإلا فإنه لا يدعم منطق كتم الصوت ، مما قد يتسبب في حدوث مشكلات في الأداء.

لذلك تقوم بإنشاء مُوفر المؤشر في عنصر واجهة المستخدم وتمريره إلى العدادات. أنا أيضًا لم أتخلص من أدوات التحكم في الرسوم المتحركة أيضًا. هذه التفاصيل تافهة جدًا في التنفيذ ولم أشعر أنها ستضيف أي شيء إلى المثال. ولكن ها نحن نتعامل معهم.

لقد قمت بتنفيذ فئة Counter () لأفعل ما يفعله مثالك ولا شيء أكثر من ذلك.

ومع هذا النهج ، يتعين علينا إعادة تنفيذ منطق توين في كل موقع يريد في الأصل إنشاء TweenAnimationBuilder. هذا يؤدي إلى تكرار كبير ، خاصة إذا أخذنا في الاعتبار أن المنطق ليس تافهًا

لما؟ لماذا ا؟ اشرح من فضلك ، لماذا لا يمكنني إنشاء أكثر من مثيل لفئة Counter واستخدامها في عناصر واجهة مستخدم مختلفة؟

Rudiksz لست متأكدًا من أن

لقد قمت بتنفيذ فئة Counter () لأفعل ما يفعله مثالك ولا شيء أكثر من ذلك.

و بعد

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

لذلك تقوم بإنشاء مُوفر المؤشر في عنصر واجهة المستخدم وتمريره إلى العدادات. أنا أيضًا لم أتخلص من أدوات التحكم في الرسوم المتحركة أيضًا. هذه التفاصيل تافهة جدًا في التنفيذ ولم أشعر أنها ستضيف أي شيء إلى المثال. ولكن ها نحن نتعامل معهم.

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

من الواضح أنك لم تحاول حتى إدارة المثال. يتصرف تمامًا مثل التعليمات البرمجية الخاصة بك.

لا تعني لا.

انت تمزح. أخذت مثالك و "تعاملت" مع ValueListenableBuilders المتداخلة * والبناة الصغار

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

تضمين التغريدة

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

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

فيما يتعلق بالمشاركات الأخيرة الأقل ترحيبًا تمامًا ، من فضلك @ TimWhiting 's repo بدلاً من القتال ذهابًا وإيابًا بأمثلة صغيرة. كما ناقشنا بالفعل ، لن تكون الأمثلة الصغيرة مفصلة بما يكفي لتكون ممثلة بما يكفي لإثبات أن البديل يعمل بشكل جيد بالفعل.

يمكنك إرسال الحل الخاص بك هناك إذا كنت تعتقد أنك قد غطيت جميع المتطلبات.

تم تغيير المتطلبات بعد وقوعها. لقد قدمت حلين. واحد يقدم حل وسط (معقول للغاية) والآخر ينفذ السلوك الدقيق الذي قدمه المثال.

لقد قدمت رمزًا يكافئ ظاهريًا فقط إصدار الخطاف rousselGit ، ومع ذلك فهو ليس مكافئًا في الواقع ،

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

إذا سمح لك بإحضار حزم خارجية إلى المناقشة ، فأنا كذلك.

قابلية إعادة الاستخدام: ألق نظرة على المثال في الريبو أدناه
https://github.com/Rudiksz/cbl_example

ملحوظة:

  • يُقصد به أن يكون عرضًا لكيفية تغليف "المنطق" للأدوات الخارجية ولديك عناصر واجهة مستخدم بسيطة ، تبدو تقريبًا بتنسيق html
  • لا يغطي كل شيء غطاء الخطافات. هذا ليس المقصود. اعتقدت أننا نناقش بدائل إطار عمل Flutter ، وليس حزمة الخطافات.
  • لا يغطي _ كل حالة استخدام يمكن أن يأتي بها كل فريق تسويق
  • ولكن ، كائن العداد نفسه مرن جدًا ، ويمكن استخدامه بشكل مستقل (انظر عنوان AppBar) ، كجزء من عنصر واجهة مستخدم معقد يحتاج إلى حساب مجاميع العدادات المختلفة ، أو جعله تفاعليًا ، أو الاستجابة لإدخالات المستخدم.
  • الأمر متروك للأدوات المستهلكة لتخصيص المقدار والقيمة الأولية والمدة ونوع الرسوم المتحركة للعدادات التي يريدون استخدامها.
  • قد تحتوي تفاصيل طريقة معالجة الرسوم المتحركة على جوانب سلبية. إذا قال فريق Flutter نعم ، باستخدام متحكمات الرسوم المتحركة والمراهقات خارج الحاجيات ، بطريقة ما يكسر الإطار ، سأعيد النظر. هناك بالتأكيد أشياء يجب تحسينها. إن استبدال موفر شريط الأسهم المخصص الذي أستخدمه بموفر آخر تم إنشاؤه بواسطة مزيج عنصر واجهة المستخدم المستهلك أمر تافه. أنا لم أفعل ذلك.
  • هذا مجرد حل بديل آخر لنمط "Builder". إذا قلت أنك بحاجة أو تريد استخدام بناة ، فلا شيء من هذا ينطبق.
  • ومع ذلك ، فإن الحقيقة هي أن هناك طرقًا لتبسيط التعليمات البرمجية ، بدون ميزات إضافية. إذا لم تعجبك ، فلا تشتريه. أنا لا أدافع عن أي من هذا لجعله في إطار العمل.

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

هذا كل شيء.

Hixie هل يجب أن أفسر تعليقك على أنه طريقة للقول إن

أيضًا ، هل يمكن أن يكون لدينا مكالمة Zoom / Google Meet؟

لست متأكدًا أيضًا من تأثير رد فعل الرموز التعبيرية على منشورك.

لا شيئ. لا علاقة له بأي شيء على الإطلاق. لماذا طرحته؟

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

أنت فقط من يستطيع معرفة ما إذا كان ذلك جيدًا بما فيه الكفاية

لا أستطيع أن أكون القاضي على ذلك. بادئ ذي بدء ، لا أعتقد أنه يمكننا التقاط المشكلة باستخدام مجموعة محدودة من التطبيقات.

لا أمانع في العمل بالطريقة التي تريدها ، لكني أحتاج إلى التوجيه لأنني لا أفهم كيف سيجعلنا ذلك نتقدم.

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

على سبيل المثال ، إذا كانت رؤية عدة منشئي متداخلين من الرهيبة للقراءة ، أو 50 سطراً من النمذجة النقية التي لديها العديد من الفرص للأخطاء ، لا تُظهر المشكلة بقوة كافية ، فلا يوجد مكان تذهب إليه.

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

بالنسبة لي ، أعتقد أننا تغلبنا على نقطتين في الأرض ، ولا أعتقد أنه يمكننا المجادلة بجدية مع أي منهما.

  1. من الصعب بطبيعتها قراءة البناة المتداخلين
  2. بخلاف أدوات الإنشاء المتداخلة ، لا توجد طريقة_ لتغليف ومشاركة الحالة التي تحتوي على خطافات دورة حياة عنصر واجهة المستخدم.

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

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

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

rousselGit هل يمكنك تقديم مثال على وجود العديد من الخطافات المتعددة؟ على سبيل المثال ، أقوم بعمل رسم متحرك باستخدام ربما العشرات من AnimationControllers. باستخدام Flutter العادي ، يمكنني القيام بما يلي:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="7">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="8">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

لكن باستخدام الخطافات ، لا يمكنني الاتصال بـ useAnimationController في حلقة. أفترض أن هذا مثال تافه لكنني لم أتمكن من العثور على الحل في أي مكان لهذا النوع من حالات الاستخدام.

تضمين التغريدة

بعض الأمثلة من تطبيقاتي التي لا تزال قيد الإنتاج (يمكن لبعض الخطافات مثل إرسال الطلب مع ترقيم الصفحات أن تدمج / تعيد البناء في خطاف واحد ولكن هذا غير ذي صلة هنا):

جلب البيانات البسيط مع ترقيم الصفحات:

    final selectedTab = useState(SelectedTab.Wallet);
    final isDrawerOpen = useValueNotifier(false);
    final pageNo = useValueNotifier(0);
    final generalData = useValueNotifier(initialData);
    final services = useXApi();
    final isLoading = useValueNotifier(false);
    final waybillData = useValueNotifier<List<WaybillResponseModel>>([]);
    final theme = useTheme();
    final router = useRouter();

    fetchNextPage() async {
      if (isLoading.value || selectedTab.value != SelectedTab.Wallet) return;
      isLoading.value = true;
      final request = WaybillRequestModel()..pageNo = pageNo.value;
      final result = await services.waybillGetList(model: request);
      if (result.isOk && result.data.length > 0) {
        pageNo.value += 1;
        waybillData.value = [...waybillData.value, ...result.data];
      }
      isLoading.value = false;
    }

    // first fetch
    useEffect(() {
      fetchNextPage();
      return () {};
    }, []);

منطق النموذج (نموذج تسجيل الدخول مع التحقق من رقم الهاتف وإعادة إرسال الموقت):

    final theme = useTheme();
    final loginState = useValueNotifier(LoginState.EnteringNumber);
    final error = useValueNotifier<String>(null);
    final phoneNumberController = useTextEditingController(text: "");
    final phoneNumberFocusNode = useMemoized(() => FocusNode(), []);
    final otpFocusNode = useMemoized(() => FocusNode(), []);
    final otpController = useTextEditingController(text: "");
    final appState = Provider.of<AppStateController>(context);
    final services = useXApi();
    final router = useRouter();
    final resendTimerValue = useValueNotifier(0);
    useEffect(() {
      var timer = Timer.periodic(Duration(seconds: 1), (t) async {
        if (resendTimerValue.value > 0) resendTimerValue.value--;
      });
      return () => timer.cancel();
    }, []);

    final doLogin = () async {
      // login
      loginState.value = LoginState.LoggingIn;
      final loginResult = await services.authLoginOrRegister(
        mobileNumber: phoneNumberController.text,
      );
      if (loginResult.isOk) {
        loginState.value = LoginState.EnteringOtpCode;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(otpFocusNode);
        });
        resendTimerValue.value = 30;
      } else {
        error.value = loginResult.errorMessage;
        loginState.value = LoginState.EnteringNumber;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(phoneNumberFocusNode);
        });
      }
    };

للرسوم المتحركة ، أعتقد أن rousselGit قدم بالفعل مثالًا كافيًا.

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

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

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

بالنسبة لحالات الاستخدام البسيطة ، أستخدم البناة ، في حالات الاستخدام المعقدة ، لا أستخدم البناة. الحجة هنا هي أنه بدون استخدام البناة ، لا توجد طريقة سهلة لكتابة تعليمات برمجية موجزة وقابلة لإعادة الاستخدام. ضمنيًا ، هذا يعني أيضًا أن البناة أمر لا بد منه ، والطريقة الوحيدة لتطوير تطبيقات Flutter. من الواضح أن هذا خطأ.

لقد عرضت للتو دليلًا عمليًا لإثبات رمز المفهوم الذي يوضح ذلك. لم تستخدم أدوات البناء أو الخطافات ، ولا تغطي 100٪ من "مجموعة المشاكل اللانهائية" التي يبدو أن مشكلة جيثب بالتحديد تريد حلها. تم استدعاؤه خارج الموضوع.
Sidenote ، إنه أيضًا فعال للغاية ، حتى بدون أي معيار أعتقد أنه يتفوق على أدوات Builder. يسعدني أن أغير رأيي إذا ثبت خطأ ذلك ، وإذا وجدت أن Mobx أصبح عقبة في الأداء ، فسوف أتخلى عن Mobx وأتحول إلى بناة الفانيليا بنبض القلب.

يعمل Hixie لصالح Google ، وعليه أن يتحلى بالصبر والأدب معك ولا يمكنه أن يناديك بسبب عدم ارتباطك. أفضل ما يمكنه فعله هو الضغط من أجل المزيد من الأمثلة.

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

ليس لدي ما أخسره ، بخلاف ربما أن يتم حظري ، لذلك لا يهمني حقًا الاتصال بك.
من الواضح أنكما مجموعة ميتة أن الخطافات هي الحل الوحيد ("لأن React تفعل ذلك") لأي مشاكل تواجهها وأنه ما لم يملأ بديل 100٪ من "مجموعة المشاكل اللانهائية" التي تتخيلها ، لن تفكر حتى في الانخراط.

هذا غير معقول ، ويظهر عدم الرغبة في الانخراط حقًا.


بالطبع ، كل ما ورد أعلاه "مجرد رأيي".

أرى فائدة الخطافات في هذا المثال ، لكنني أعتقد أنني لا أفهم كيف ستعمل في حالتي حيث يبدو أنك تريد تهيئة العديد من الكائنات مرة واحدة ، في هذه الحالة AnimationController s لكن في الواقع يمكن أن يكون أي شيء. كيف تتعامل الخطافات مع هذه الحالة؟

في الأساس هناك طريقة خطاطيف لقلب هذا

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

إلى

var xs = []
for (int i = 0; i < 3; i++)
     xs[i] = useState(i);

دون انتهاك قواعد الخطاف؟ لأنني أدرجت المكافئ في الرفرفة العادية. لست خبيرًا جدًا في التعامل مع الخطافات في Flutter ، لذا احمل معي هناك.

أريد ببساطة إنشاء مجموعة من الكائنات الخطافية (على سبيل المثال ، AnimationControllers) عند الطلب مع كل من initState والتخلص منها بالفعل ، لست متأكدًا من كيفية عملها في الخطافات.

satvikpendem فكر في الخطافات مثل خصائص الفصل. هل تحددها في حلقة أو تسميها يدويًا واحدة تلو الأخرى؟

في مثالك لتعريف مثل هذا

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

مفيد لحالة الاستخدام هذه:

var isLoading = useState(1);
var selectedTab = useState(2);
var username = useState(3); // text field

هل ترى كيف يرتبط كل useState بجزء مسمى من منطق حالتك؟ (مثل اتصال isLoading's useState عندما يكون التطبيق في حالة تحميل)

في المقتطف الثاني ، تقوم بالاتصال بـ useState في حلقة. أنت تفكر في useState كحامل قيمة وليس جزءًا من منطق الدولة الخاص بك. هل هذه القائمة مطلوبة لإظهار مجموعة من العناصر في ListView ؟ إذا كانت الإجابة بنعم ، فعليك التفكير في كل عنصر في القائمة على أنه حالة وليست فردية.

final listData = useState([]);

هذا مخصص فقط لـ useState ويمكنني رؤية بعض حالات الاستخدام (التي أعتقد أنها نادرة جدًا) لاستدعاء بعض الخطافات في حالة أو في حلقة. بالنسبة لهذا النوع من الخطافات ، يجب أن يكون هناك خطاف آخر لمعالجة قائمة البيانات بدلاً من واحدة. على سبيل المثال:

var single = useTest("data");
var list = useTests(["data1", "data2"]);
// which is equivalent to
var single1 = useTest("data1");
var single2 = useTest("data2");

أرى أنه مع الخطافات ، يبدو أننا بحاجة إلى إنشاء خطاف منفصل للتعامل مع الحالات مع مجموعة من العناصر ، مثل AnimationControllers المتعددة.

هذا ما كان لدي في البداية والذي لا يبدو أنه يعمل:

  final animationControllers = useState<List<AnimationController>>([]);

  animationControllers.value = List<AnimationController>.generate(
    50,
    (_) => useAnimationController(),
  );

لكنني أفترض أنه إذا قمت بكتابة الخطاف الخاص بي للتعامل مع عناصر متعددة ، فيجب أن ينجح هذا ، أليس كذلك؟

class _MultipleAnimationControllerHook extends Hook<MultipleAnimationController> {
  const _MultipleAnimationControllerHook({
    this.numControllers,
    this.duration,
    this.debugLabel,
    this.initialValue,
    this.lowerBound,
    this.upperBound,
    this.vsync,
    this.animationBehavior,
    List<Object> keys,
  }) : super(keys: keys);

  /// Take in number of controllers wanted
  /// This hook assumes all `AnimationController`s will have the same parameters
  final int numControllers; 

  final Duration duration;
  final String debugLabel;
  final double initialValue;
  final double lowerBound;
  final double upperBound;
  final TickerProvider vsync;
  final AnimationBehavior animationBehavior;

  <strong i="10">@override</strong>
  _AnimationControllerHookState createState() =>
      _AnimationControllerHookState();
}

class _AnimationControllerHookState
    extends HookState<AnimationController, _AnimationControllerHook> {
  List<AnimationController> _multipleAnimationController; // return a list instead of a singular item

  <strong i="11">@override</strong>
  void initHook() {
    super.initHook();
    for (int i = 0; i < hook.numControllers) // just added loops 
        _multipleAnimationController[i] = AnimationController(
          vsync: hook.vsync,
          duration: hook.duration,
          debugLabel: hook.debugLabel,
          lowerBound: hook.lowerBound,
          upperBound: hook.upperBound,
          animationBehavior: hook.animationBehavior,
          value: hook.initialValue,
        );
  }

  <strong i="12">@override</strong>
  void didUpdateHook(_AnimationControllerHook oldHook) {
      for (int i = 0; i < numControllers; i++) {
        if (hook.vsync != oldHook[i].vsync) {
           _multipleAnimationController[i].resync(hook.vsync);
        }

        if (hook.duration != oldHook[i].duration) {
          _multipleAnimationController[i].duration = hook.duration;
        }
      }
  }

  <strong i="13">@override</strong>
  MultipleAnimationController build(BuildContext context) {
    return _multipleAnimationController;
  }

  <strong i="14">@override</strong>
  void dispose() {
    _multipleAnimationController.map((e) => e.dispose());
  }
}

هل هذا يعني أنه إذا كان لدينا نسخة مفردة من الخطاف ، فلا يمكننا ببساطة استخدامها لنسخة تحتوي على عناصر متعددة وبدلاً من ذلك يتعين علينا إعادة كتابة المنطق؟ أم أن هناك طريقة أفضل للقيام بذلك؟

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

تضمين التغريدة

لا أعتقد أن البيان الخاص بي صالح لـ AnimationController أو useAnimationController

لأنه على الرغم من أنه قد يكون لديك أكثر من AnimationController ولكن لا يجب عليك تخزينها في مصفوفة لاستخدامها في التابع class. على سبيل المثال:

useSingleTickProvider();
final animation1 = useAnimationController();
final animation2 = useAnimationController();
final animation3 = useAnimationController();
// ...
useEffect(() async {
  await animation1.forward();
  await Future.sleep(100);
  await animation1.reverse();
  await animation2.forward();
  await animation3.forward();
}, []);

(لا تقوم بإنشاء قائمة والإشارة إليها مثل animation[0] )

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

للإجابة على سؤالك إذا كانت هناك طريقة أسهل للتعامل مع عدة AnimationController ، نعم هناك:

final ticker = useTickerProvider();
final controllers = useMemo(() => [AnimationController(ticker), AnimationController(ticker)], []);

useEffect(() {
  controllers.forEach(x => x.resync(ticker));
  return () => controllers.forEach(x => x.dispose());
}, [ticker, controllers]);

  • يمكنك أيضًا استخدام useState إذا كان AnimationController s ديناميكيًا.

(تتم إعادة المزامنة أيضًا عند تغيير المؤشر)

rousselGit هل يمكنك تقديم مثال على وجود العديد من الخطافات المتعددة؟ على سبيل المثال ، أقوم بعمل رسم متحرك باستخدام ربما العشرات من AnimationControllers. باستخدام Flutter العادي ، يمكنني القيام بما يلي:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="8">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="9">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

لكن باستخدام الخطافات ، لا يمكنني الاتصال بـ useAnimationController في حلقة. أفترض أن هذا مثال تافه لكنني لم أتمكن من العثور على الحل في أي مكان لهذا النوع من حالات الاستخدام.

الخطافات تفعل ذلك بشكل مختلف.

لم نعد ننشئ قائمة بوحدات التحكم ، بل ننقل منطق وحدة التحكم إلى العنصر:

Widget build(context) {
  return ListView(
    children: [
      for (var i = 0; i < 50; i++)
        HookBuilder(
          builder: (context) {
            final controller = useAnimationController();
          },
        ),
    ],
  );
}

ما زلنا نصنع 50 وحدة تحكم في الرسوم المتحركة الخاصة بنا ، لكنها مملوكة لعنصر واجهة مستخدم مختلف.

ربما يمكنك مشاركة مثال على سبب احتياجك لذلك ، ويمكننا محاولة التحويل إلى الخطافات وإضافته إلى الريبو الخاص بـ Tim؟

يعمل Hixie لصالح Google ، وعليه أن يتحلى بالصبر والأدب معك ولا يمكنه أن يناديك بسبب عدم ارتباطك. أفضل ما يمكنه فعله هو الضغط من أجل المزيد من الأمثلة.

Hixie ، إذا كان هذا هو ما تشعر به ، من فضلك قل ذلك (إما هنا أو الاتصال بي بشكل خاص).

لقد قدمت حرفياً أمثلة عن كيفية تقليل الإسهاب وتجنب تداخل البناة باستخدام مثال قدمته Remi.

شكرًا ، لكن ليس من الواضح بالنسبة لي كيف يمكنك استخراج نمط شائع من هذا الرمز عند تطبيق هذا المنطق على حالات الاستخدام المختلفة.

في OP ، ذكرت أنه حاليًا ، لدينا 3 خيارات:

  • استخدام البناة ولديهم كود متداخل
  • لا تحلل الكود على الإطلاق ، والذي لا يتناسب مع منطق الحالة الأكثر تعقيدًا (أود أن أزعم أن StreamBuilder و AsyncSnapshot هو منطق حالة معقد).
  • حاول تكوين بعض الهندسة المعمارية باستخدام mixins / oop / ... ، ولكن ينتهي بك الأمر بحل خاص جدًا بالمشكلة التي تتطلب إعادة كتابة أي حالة استخدام مختلفة قليلاً.

يبدو لي أنك استخدمت الخيار الثالث (الذي يقع في نفس الفئة مثل التكرارات المبكرة للمقترحات Property أو addDispose ).

لقد قمت مسبقًا بعمل شبكة تقييم للحكم على النمط:

هل يمكنك تشغيل البديل الخاص بك على هذا؟ خاصة التعليق الثاني حول تنفيذ جميع ميزات StreamBuilder بدون تكرار الكود إذا تم استخدامه عدة مرات.

خطتي في هذه المرحلة بشأن هذا الخطأ هي:

  1. خذ الأمثلة من https://github.com/flutter/flutter/issues/51752#issuecomment -675285066 وأنشئ تطبيقًا باستخدام Flutter النقي الذي يعرض حالات الاستخدام المختلفة معًا.
  2. حاول تصميم حل يتيح إعادة استخدام الكود لتلك الأمثلة التي تفي بالمتطلبات الرئيسية المختلفة التي تمت مناقشتها هنا والتي تتناسب مع مبادئ التصميم الخاصة بنا.

إذا رغب أي شخص في المساعدة في أيٍّ من هذين الأمرين ، فأنا بالتأكيد سعيد بالحصول على المساعدة. من غير المحتمل أن أصل إلى هذا قريبًا لأنني أعمل على انتقال NNBD أولاً.

rousselGit بالتأكيد ، أقوم بإنشاء تطبيق حيث يمكن أن تتحرك العديد من الأدوات Box es) ، ويجب أن يكونوا قادرين على التحرك بشكل مستقل عن بعضهم البعض (لذلك يجب أن يكون هناك على الأقل AnimationController واحد لكل Box ). إليك إصدار واحد قمت بإنشائه باستخدام AnimationController واحد فقط تمت مشاركته بين العديد من عناصر واجهة المستخدم ، ولكن في المستقبل قد أقوم بتحريك كل عنصر واجهة مستخدم بشكل مستقل ، على سبيل المثال للقيام بتحولات معقدة مثل تنفيذ CupertinoPicker ، مع تأثير عجلة التمرير المخصصة الخاصة بها .

توجد ثلاثة مربعات في المكدس تتحرك لأعلى ولأسفل عند النقر فوق الزر العائم.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main(List<String> args) => runApp(const App());

class App extends HookWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    final AnimationController controller =
        useAnimationController(duration: duration);
    final Animation<double> animation = Tween<double>(
      begin: 0,
      end: 300,
    )
        .chain(
          CurveTween(
            curve: curve,
          ),
        )
        .animate(controller);
    final ValueNotifier<bool> isDown = useState<bool>(false);
    final ValueNotifier<int> numBoxes = useState<int>(3);

    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown.value) {
                controller.forward();
                isDown.value = true;
              } else {
                controller.reverse();
                isDown.value = false;
              }
            },
          ),
          body: AnimatedBuilder(
            animation: animation,
            builder: (_, __) => Boxes(
              numBoxes: numBoxes.value,
              animation: animation,
            ),
          ),
        ),
      ),
    );
  }
}

class Boxes extends StatelessWidget {
  Boxes({
    <strong i="12">@required</strong> this.numBoxes,
    <strong i="13">@required</strong> this.animation,
  });

  final int numBoxes;
  final Animation<double> animation;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Stack(
      children: List<Widget>.generate(
        numBoxes,
        (int index) => Positioned(
          top: (animation.value) + (index * (100 + 10)),
          left: (MediaQuery.of(context).size.width - 100) / 2,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
      // ],
    );
  }
}

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

يبدو أن المشكلة تكمن في أن AnimationControllers و Box es التي تستخدمهما لقيادة حركتها ليست في نفس الفئة ، لذلك قد يحتاج المرء إما إلى المرور عبر AnimationController عن طريق الاحتفاظ بمصفوفة منها لاستخدامها في منشئ ، أو اجعل كل Box يحافظ على AnimationController الخاص به.

باستخدام الخطافات ، نظرًا لأن Box es والأداة الرئيسية ليسا في نفس الفئة ، كيف يمكنني عمل قائمة متحكمات الرسوم المتحركة للحالة الأولى حيث يتم تمرير كل Box في متحكم AnimationController؟ يبدو أن هذا ليس ضروريًا بناءً على إجابتك أعلاه مع HookBuilder ، ولكن بعد ذلك إذا انتقلت إلى الحالة الفرعية إلى القطعة الفرعية كما قلت ، واخترت أن يكون لكل Box متحكم الرسوم المتحركة الخاص به عبر useAnimationController ، واجهت مشكلة أخرى: كيف سأعرض AnimationController الذي تم إنشاؤه للفئة الأصلية لتنسيق الرسوم المتحركة المستقلة وتشغيلها لكل طفل؟

في Vue ، يمكنك إرسال حدث إلى الوالد عبر النمط emit ، لذا في Flutter هل أحتاج إلى بعض حلول إدارة الحالة الأعلى مثل Riverpod أو Rx حيث يقوم الوالد بتحديث الحالة العالمية ويستمع الطفل إلى الوضع العالمي حالة؟ يبدو أنه لا ينبغي لي ، على الأقل لمثال بسيط مثل هذا. شكرا لتوضيح حيرتي.

satvikpendem آسف لم أكن واضحا. هل يمكنك إظهار كيف ستفعل ذلك بدون خطافات ، بدلاً من المشكلة التي تمنعك من استخدام الخطافات؟

أريد أن أحصل على فهم واضح لما تحاول القيام به بدلاً من المكان الذي تتعثر فيه

ولكن كتخمين سريع ، أعتقد أنك تبحث عن منحنى الفاصل الزمني بدلاً من ذلك ، ولديك وحدة تحكم واحدة للرسوم المتحركة.

rousselGit بالتأكيد ،

import 'package:flutter/material.dart';

void main(List<String> args) => runApp(const App());

class Animator {
  Animator({this.controller, this.animation});
  AnimationController controller;
  Animation<double> animation;
}

class App extends StatefulWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="7">@override</strong>
  _AppState createState() => _AppState();
}

class _AppState extends State<App> with TickerProviderStateMixin {
  List<Animator> animators = [];
  bool isDown = false;
  int numBoxes = 3;

  <strong i="8">@override</strong>
  void initState() {
    for (int i = 0; i < numBoxes; i++) {
      final AnimationController c = AnimationController(
        duration: App.duration,
        vsync: this,
      );
      animators.add(
        Animator(
          controller: c,
          animation: Tween<double>(
            begin: 0,
            end: 300,
          )
              .chain(
                CurveTween(
                  curve: App.curve,
                ),
              )
              .animate(c),
        ),
      );
    }
    super.initState();
  }

  <strong i="9">@override</strong>
  void dispose() {
    for (int i = 0; i < numBoxes; i++) {
      animators[i].controller.dispose();
    }
    super.dispose();
  }

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown) {
                for (final Animator animator in animators) {
                  animator.controller.forward();
                }
                setState(() {
                  isDown = true;
                });
              } else {
                for (final Animator animator in animators) {
                  animator.controller.reverse();
                }
                setState(() {
                  isDown = false;
                });
              }
            },
          ),
          body: Stack(
            children: List<Box>.generate(
              numBoxes,
              (int index) => Box(
                index: index,
                animation: animators[index].animation,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Box extends StatelessWidget {
  Box({
    <strong i="11">@required</strong> this.animation,
    <strong i="12">@required</strong> this.index,
  });

  final int index;
  final Animation<double> animation;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return Positioned(
      top: (animation.value) + (index * (100 + 10)),
      left: (MediaQuery.of(context).size.width - 100) / 2,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  }
}

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

مع Flutter العادي ، يمكن أن يكون لكل من initState و dispose حلقات ولكن ليس هكذا يبدو مع الخطافات ، لذلك أنا فقط أتساءل كيف يمكن للمرء مكافحة ذلك. كذلك ، لا أريد وضع فئة Box داخل عنصر واجهة المستخدم الأصلي ، حيث لا أريد تغليفهما بإحكام ؛ يجب أن أكون قادرًا على الاحتفاظ بالمنطق الأصلي كما هو مع استبدال Box بـ Box2 على سبيل المثال.

شكرا!
لقد دفعت بمثالك إلى

TL ؛ DR ، مع الخطافات (أو البناة) ، نفكر بشكل تصريحي بدلاً من الضرورة. لذلك بدلاً من وجود قائمة بوحدات التحكم على عنصر واجهة مستخدم واحد ، ثم توجيهها بشكل إلزامي - والتي تنقل وحدة التحكم إلى العنصر وتنفذ رسمًا متحركًا ضمنيًا.

شكراrousselGit! كنت أعاني من هذا النوع من التنفيذ لفترة قصيرة بعد أن بدأت في استخدام الخطافات ولكني أفهم الآن كيف يعمل. لقد فتحت للتو PR لإصدار مع هدف مختلف لكل وحدة تحكم في الرسوم المتحركة لأن ذلك قد يكون أكثر إلحاحًا لفهم سبب فائدة الخطافات كما قلت أعلاه:

يمكننا أن نتخيل سيناريو حيث بدلاً من أن يكون لكل منهم نفس المنحنى ، أعطي لكل منهم منحنى مختلف ، أو أقوم بعمل 100 صندوق ، كل منها بمدة أطول أو أقصر من سابقتها ، أو أقوم بزيادة العدد المتساوي والأخرى تنخفض ، وهكذا.

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

جئت عبر مثال من العالم الحقيقي في قاعدة الشفرة الخاصة بي اليوم ، لذلك اعتقدت أنني قد أشاركها أيضًا.

لذا في هذا السيناريو ، أعمل مع Firestore ، ولدي بعض النماذج المعيارية التي أرغب في تنفيذها مع كل StreamBuilder ، لذلك قمت بإنشاء مُنشئ مخصص خاص بي. أحتاج أيضًا إلى العمل مع ValueListenableمما يسمح للمستخدم بإعادة ترتيب القائمة. لأسباب تتعلق بالتكلفة النقدية المتعلقة بـ Firestore ، يتطلب هذا تنفيذًا محددًا للغاية (لا يمكن لكل عنصر تخزين طلبه الخاص به ، وبدلاً من ذلك يجب على القائمة حفظه كحقل للمعرفات المتسلسلة) ، وذلك لأن firestore تفرض رسومًا على كل عملية كتابة ، لذلك يمكنك توفير الكثير من المال بهذه الطريقة. ينتهي الأمر بقراءة شيء مثل هذا:

return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
        backgroundColor: state.theme.scaffoldBackgroundColor,
        body: FamilyStreamBuilder<DocumentSnapshot>(
          stream: state.listRef.snapshots(),
          builder: (context, AsyncSnapshot<DocumentSnapshot> documentSnapshot) {
            //When a list is updated, we need to update the listOrder
            state.updateListOrderFromSnapshot(documentSnapshot);
            return FamilyStreamBuilder<QuerySnapshot>(
                stream: state.itemsCollection.snapshots(),
                builder: (_, AsyncSnapshot<QuerySnapshot> itemsSnapshot) {
                  //Sort the list items by the idField on the list-doc
                  List<DocumentSnapshot> items = itemsSnapshot.data.documents;
                  state.handleDocsSync(items);
                  return ValueListenableBuilder(
                    valueListenable: state.listOrderNotifier,
                    builder: (_, List<String> listOrder, __) {
                      List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);
                     //Set the firstCompleted item if we have one
                      state.firstCompletedItem = ordered
                          ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
                          ?.documentID;
                      return _buildItemList(items, ordered);
                    },
                  );
                });
          },
        ),
      ),
    );

يبدو أنه سيكون من الأسهل بكثير التفكير ، إذا كان بإمكاني كتابته مثل:

    DocumentSnapshot list = useFamilyStream(state.listRef.snapshots());
    List<DocumentSnapshot> items = useFamilyStream(state.itemsCollection.snapshots()).data.documents;
    List<String> listOrder = useValueListenable(state.listOrderNotifier);

    //When a list is updated, we need to update the listOrder
    state.updateListOrderFromSnapshot(list);

   //Sort the list items by the idField on the list-doc
    state.handleDocsSync(items);
    List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);

    //Set the firstCompleted item if we have one
    state.firstCompletedItem = ordered
        ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
        ?.documentID;

    return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
          backgroundColor: state.theme.scaffoldBackgroundColor,
          body: _buildItemList(items, ordered)
      ));

هذا يفقد التحسين فيما يتعلق بإعادة البناء الحبيبية ، لكن هذا لن يجعل أي IRL مختلفًا نظرًا لأن جميع العناصر المرئية في العقدة الطرفية السفلية ، جميع الأغلفة هي حالة نقية.

كما هو الحال مع العديد من سيناريوهات العالم الواقعي ، فإن نصيحة "لا تستخدم X فقط" ليست واقعية ، حيث أن Firebase لديه طريقة واحدة فقط للاتصال وهي التدفقات ، وفي أي وقت أريد هذا السلوك الشبيه بالمقبس ، ليس لدي خيار سوى استخدام الدفق. هذه هي الحياة.

هذا يفقد التحسين فيما يتعلق بإعادة البناء الحبيبية ، لكن هذا لن يجعل أي IRL مختلفًا نظرًا لأن جميع العناصر المرئية في العقدة الطرفية السفلية ، جميع الأغلفة هي حالة نقية.

لا يزال هناك فرق. سواء كانت العقدة مرئية أم لا ، لا يؤثر على ما إذا كانت تكلف شيئًا ما لإعادة بنائها.

من المحتمل أن أدرج هذا المثال في عناصر واجهة مستخدم مختلفة (تحتوي IDE على أدوات إعادة البناء بنقرة واحدة لتسهيل ذلك حقًا). _buildItemList المحتمل أن يكون FamilyStreamBuilder .

نحن لا نفقد حقًا إعادة البناء الحبيبية.
في الواقع ، تعمل الخطافات على تحسين هذا الجانب ، من خلال السماح بالتخزين المؤقت لمثيل الأداة بسهولة باستخدام useMemoized .

هناك بعض الأمثلة على إعادة الشراء الخاصة بـ Tim التي تقوم بذلك.

من المحتمل أن أدرج هذا المثال في عناصر واجهة مستخدم مختلفة (تحتوي IDE على أدوات إعادة البناء بنقرة واحدة لتسهيل ذلك حقًا). _buildItemList المحتمل أن يكون FamilyStreamBuilder .

الشيء هو أنني لا أريد فعل ذلك حقًا ، لأنه ليس لدي أي مخاوف تتعلق بالأداء على الإطلاق في وجهة النظر هذه. لذلك ، 100٪ من الوقت سأفضل موقع الكود والتماسك على التحسين الجزئي مثل هذا. يُعاد بناء هذا العرض فقط عندما يبدأ المستخدم الإجراء (~ متوسط ​​مرة واحدة لكل 10 ثوانٍ) ، أو عندما تتغير بيانات الواجهة الخلفية ويحدقوا في قائمة مفتوحة (تقريبًا لا يحدث أبدًا). يحدث أيضًا أن يكون مجرد عرض بسيط يمثل قائمة بشكل أساسي ، وتحتوي القائمة على الكثير من التحسينات الخاصة بها التي تجري داخليًا. أدرك أن build () يمكن إطلاقه تقنيًا في أي وقت ، ولكن من الناحية العملية ، فإن أي عمليات إعادة بناء عشوائية نادرة جدًا.

من السهل جدًا العمل على طريقة العرض هذه وتصحيحها إذا تم تجميع كل هذا المنطق في عنصر واجهة مستخدم واحد ، وذلك في الأساس محاولة لجعل حياتي أسهل عندما أعود إليها في المستقبل :)

شيء آخر يجب ملاحظته هو أن التعشيش بشكل أساسي "أجبرني على الخروج" من طريقة البناء ، حيث لم يكن هناك طريقة يمكنني من خلالها البدء في بناء شجرتى داخل 3 أغلاق و 16 مساحة في الحفرة.

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

تذكر في هذا السيناريو أنني قمت بالفعل بإنشاء عنصر واجهة مستخدم مخصص للتعامل مع Stream Builder. الآن سأحتاج إلى عمل واحد آخر للتعامل مع تكوين هؤلاء البناة ؟؟ يبدو قليلا فوق القمة.

لأنه ليس لدي أي مخاوف تتعلق بالأداء على الإطلاق في هذا الرأي

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

لا يمكن بأي حال من الأحوال أن أبدأ في بناء شجرتى داخل 3 أغلاق و 16 فراغًا في الحفرة

قد يكون لدي فقط شاشة أوسع منك ...: - /

ثم ليست هناك حاجة إلى سهولة القراءة ومتاعب الصيانة لتقسيم الأشياء عبر ملفين

أود أن أضع الأدوات في نفس الملف ، FWIW.

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

قد يكون لدي فقط شاشة أوسع منك ...: - /

لدي فائق السرعة: D ، ولكن من الواضح أن dartfmt يحدنا جميعًا عند 80. لذا فإن خسارة 16 أمر مهم. المشكلة الرئيسية هي أن نهاية بياني هي أن هذا },);});},),),); ليس ممتعًا حقًا عندما يتم العبث بشيء ما. يجب أن أكون حذرًا للغاية في أي وقت أقوم فيه بتعديل هذا التسلسل الوراثي ، ويتوقف مساعدو IDE الشائعون مثل swap with parent عن العمل.

أود أن أضع الأدوات في نفس الملف ، FWIW.

100٪ ، لكني ما زلت أجد أن القفز عموديًا في ملف واحد أصعب. لا مفر منه بالطبع ، لكننا نحاول التقليل عندما يكون ذلك ممكنًا و "الحفاظ على الأشياء معًا".

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

من الواضح أن dartfmt يقصرنا جميعًا على 80

أعني ، هذا أحد الأسباب التي تجعلني لا أستخدم dartfmt بشكل عام ، وعندما أقوم بتعيينه على 120 أو 180 حرفًا ...

تجربتك هنا صالحة تماما.

أنا أيضًا في الواقع ، 120 طوال اليوم :) لكن pub.dev بنشاط إضافات ذات معدلات منخفضة لم يتم تنسيقها في 80 ، ولدي انطباع بأنني (نحن) أقلية عندما نغير هذه القيمة.

حسنًا ، هذا سخيف ، يجب أن نصلح ذلك.

لا يحترم pub.dev الإضافات منخفضة السعر التي لا تحترم dartfmt. يظهر فقط تعليقًا في صفحة النتيجة ، لكن النتيجة غير متأثرة
ولكن يمكن القول أن هناك مشاكل مع dartfmt أكثر من مجرد طول الخط.

يؤدي طول السطر الكبير جدًا إلى جعل الأشياء التي يمكن قراءتها أكثر في سطور متعددة في سطر واحد ، مثل:

object
  ..method()
  ..method2();

والتي قد تصبح:

object..method()..method2();

أنا أرى هذا؟
image
الحزمة المعنية: https://pub.dev/packages/sized_context/score

مثير للاهتمام - لم يكن الأمر كذلك من قبل ، حيث لم يستخدم المزود dartfmt لفترة من الوقت.
أنا أقف بشكل صحيح.

نعم ، إنه بالتأكيد سلوك جديد ، عندما نشرت في الربيع الماضي ، تأكدت من أنني كنت أضع علامة في جميع المربعات ، ولم يكن dartfmt مطلوبًا.

بعد كل هذه المناقشات ، آمل أن نرى دعمًا محليًا لحل يشبه الخطاف في الرفرفة. إما useHook أو use Hook أو أي شيء يشعر به فريق الرفرفة أن ميزته ليست مثل React 😁🤷‍♂️

نستخدم الخطافات بطريقة مثل final controller = useAnimationController(duration: Duration(milliseconds: 800));
أليس من الأفضل استخدام ميزة Darts الجديدة للبرنامج _Extension_ المنسوخة من kotlin / swift إلى هذا النحو الجميل؟

شيء من هذا القبيل: final controller = AnimationController.use(duration: Duration(milliseconds: 800));
مع هذا النهج، عندما رفرفة / ثبة فريق يقرر إضافة use Hook بدلا من عرضه في بناء الجملة متاح useHook ، وأعتقد أن Annotation إلى أن وظيفة الإرشاد جعلت من قراءة لاستخدام
final controller = use AnimationController(duration: Duration(milliseconds: 800));

من المفهوم / المفيد أيضًا استخدام كلمة رئيسية use مثل const و new :
new Something
const Something
use Something

كمكافأة لهذه التوصية ، أعتقد أخيرًا أنه حتى وظائف المُنشئ / المُنشئ يمكنها استخدام / الاستفادة من ذلك Annotation المقترح. ثم يحول dart compiler مع بعض التخصيصات إلى دعم الكلمة الرئيسية use .

جميلة جدا ورفرفة / دارت ميزة محددة 😉

هل أنا محق في افتراض أن الأمثلة الموجودة في https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful تمثل الآن المشكلات التي يريد الناس حلها؟

لست متأكدًا مما يشعر به الآخرون ، لكنني أعتقد أن المشكلات ممثلة إلى حد ما هناك (مما يعني أنني لا أستطيع أن أكون متأكدًا لأن شخصًا ما قد يشير إلى شيء غير ممثل).

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

لقد أطلقت عليه نهج دورة الحياة ميكسين ، إنه قريب جدًا من نهج LateProperty الذي تمت مناقشته هنا ، ولكن الاختلاف الرئيسي هو أنه يحتوي على المزيد من دورات الحياة التي تم تنفيذها ، ويمكن أن يؤلف بسهولة. (فيما يتعلق بجزء دورات الحياة ، لم أستخدم دورات حياة عنصر واجهة المستخدم بخلاف initState وأتخلص منها كثيرًا ، لذلك ربما أكون قد أفسدت الأمر تمامًا هناك).

يعجبني هذا النهج للأسباب التالية:

  1. لديها القليل جدا من عقوبة وقت التشغيل.
  2. لا يوجد منطق / وظائف تنشئ أو تدير حالة في مسار البناء (يمكن أن تكون البنيات خالصة - فقط حالة الجلب).
  3. تكون إدارة دورة الحياة أكثر وضوحًا عند تحسين عمليات إعادة البناء عبر المُنشئ. (لكنك لا تضحي بإمكانية إعادة الاستخدام والتركيب لأجزاء صغيرة من الحالة).
  4. نظرًا لأنه يمكنك إعادة استخدام إنشاء أجزاء من الحالة ، يمكن إنشاء مكتبة من أجزاء مشتركة من الحالة يجب إنشاؤها والتخلص منها بطرق معينة ، لذلك هناك عدد أقل من النماذج المعيارية في التعليمات البرمجية الخاصة بك.

لا يعجبني هذا الأسلوب (بالمقارنة مع الخطافات) للأسباب التالية:

  1. لا أعرف ما إذا كان يغطي كل شيء يمكن أن تفعله الخطافات.
  2. لديك لاستخدام مفاتيح لتحديد فريد الخصائص. (لذلك عند تكوين أجزاء المنطق التي تبني بعض الحالات ، يجب عليك إلحاق المفتاح لتحديد كل جزء من الحالة بشكل فريد - مما يجعل المفتاح معلمة موضعية مطلوبة ، لكني أحب حل مستوى اللغة للوصول إلى معرف فريد للمتغير).
  3. يستخدم بشكل كبير ملحقات لإنشاء وظائف قابلة لإعادة الاستخدام لإنشاء أجزاء مشتركة من الحالة. ولا يمكن استيراد الامتدادات تلقائيًا بواسطة IDEs.
  4. يمكنك أن تفسد نفسك إذا قمت بخلط دورات حياة عناصر واجهة مستخدم مختلفة / قم بالوصول إليها بين عناصر واجهة المستخدم دون إدارتها بشكل صريح بشكل صحيح.
  5. يعتبر بناء جملة الباني غريبًا بعض الشيء ، لذا فإن الحالة التي تم إنشاؤها في نطاق وظيفة البناء ، مع ترك وظيفة البناء نقية.
  6. لم أقم بتطبيق جميع الأمثلة حتى الآن ، لذلك قد تكون هناك حالة استخدام لا يمكنني تغطيتها.

مثال بسيط على العداد .
مثال على العدادات المتحركة

إطار العمل
أجزاء مشتركة من حالة قابلة لإعادة الاستخدام تأليف المنطق

لست متأكدًا من مقدار الوقت المتاح لي ، فالدراسات العليا تجعلني مشغولًا دائمًا ، لكني أحب بعض التعليقات. rousselGit ما مدى قرب هذا من الخطافات ، هل يمكنك رؤية بعض الثغرات الواضحة في إعادة الاستخدام أو القابلية للتركيب؟

لا أحاول الترويج للحل ، بقدر ما أشجع المناقشة الإيجابية على أرضية مشتركة. إذا تمكنا من الاتفاق على ما هو مفقود أو ما يقدمه لنا هذا الحل ، أعتقد أننا سنحقق تقدمًا جيدًا إلى الأمام.

TimWhiting المشكلة الرئيسية التي

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

كل شيء آخر هو مجرد تباين في حالة استخدام نفس حالة استخدام "Stateful Controller". أريد تنفيذ X في initState ، و Y في حالة التخلص ، وتحديث Z عندما تتغير تبعياتي. لا يهم ما هي X و Y و Z.

أتساءل عما إذا كان بإمكان rousselGit تقديم بعض الأفكار هنا ، أو لديه أي بيانات حول الخطافات الأكثر استخدامًا حاليًا. أعتقد أنه 80٪ من البث والرسوم المتحركة ، ولكن سيكون من الجيد معرفة ما يستخدمه الناس أكثر من غيرهم.

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

تمامًا كما نفعل:

var anim = get AnimationController();
return Column(
  _someExpensiveBuildMethod(),
  FadeTransition(opacity: anim, child: ...)
)

يمكننا دائمًا القيام بما يلي:

var foo = get ComplicatedThingController();
return Column(
  _someExpensiveBuildMethod(),
  ComplicatedThing(controller: foo, child: ...)
)

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

هل أنا محق في افتراض أن الأمثلة الموجودة في https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful تمثل الآن المشكلات التي يريد الناس حلها؟

أنا بصراحة لست متأكدا.
وأود أن أقول نعم_. لكن هذا يعتمد حقًا على كيفية تفسير هذه الأمثلة.

في هذا الموضوع ، لدينا تاريخ من عدم فهم بعضنا البعض ، لذلك لا يمكنني ضمان عدم حدوث ذلك مرة أخرى.

هذا جزئيًا سبب كره استخدام أمثلة التعليمات البرمجية واقترح استخراج مجموعة من القواعد بدلاً من ذلك.
الأمثلة ذاتية ولديها حلول متعددة ، بعضها قد لا يحل المشكلة الأوسع.

أتساءل عما إذا كان بإمكان rousselGit تقديم بعض الأفكار هنا ، أو لديه أي بيانات حول الخطافات الأكثر استخدامًا حاليًا. أعتقد أنه 80٪ من البث والرسوم المتحركة ، ولكن سيكون من الجيد معرفة ما يستخدمه الناس أكثر من غيرهم.

أعتقد أنها متجانسة للغاية.

على الرغم من وجود أي شيء ، فمن المحتمل أن تكون الرسوم المتحركة هي الأقل استخدامًا: useStream والرسوم المتحركة:

  • عادةً ما يكون لـ useStream مكافئ أفضل اعتمادًا على البنية الخاصة بك. يمكن استخدام context.watch ، useBloc ، useProvider ، ...
  • قلة من الناس تأخذ الوقت الكافي لعمل الرسوم المتحركة. وهذا نادرا ما يكون أولوية، و TweenAnimationBuilder تغطي الحاجيات الأخرى المتحركة ضمنا جزء كبير من الحاجة.
    ربما سيتغير ذلك إذا أضفت خطافتي useImplicitlyAnimatedInt في flutter_hooks.

esDotDev أزلت للتو الحاجة إلى المفاتيح / المعرفات في نهج دورة الحياة

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

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

على سبيل المثال ، ضع في اعتبارك كيف ، إذا كان var anim = AnimationController.use(context, duration: widget.duration ?? _duration); مواطنًا من الدرجة الأولى ، فلن تحتاج فعليًا إلى وجود أي من هذه الرسوم المتحركة الضمنية أو الصريحة. إنها تجعلها زائدة عن الحاجة لأنها تم إنشاؤها جميعًا لإدارة المشكلة الأساسية: تكوين شيء ذي حالة (AnimationController) بسهولة في سياق عنصر واجهة مستخدم. أصبح TAB قريبًا جدًا من العبث ، حيث يمكنك فعل الشيء نفسه باستخدام AnimatedBuilder + AnimatorController.use() .

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

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

مقابل ما يستحق ، أستخدم شيئًا مثل useAnimation بكثافة في تطبيقي بدلاً من أدوات الرسوم المتحركة العادية. هذا لأنني أستخدم SpringAnimation غير مدعوم جيدًا مع عناصر واجهة مستخدم مثل AnimatedContainer على سبيل المثال ؛ يفترضون جميعًا رسومًا متحركة تستند إلى الوقت ، باستخدام curve و duration بدلاً من الرسوم المتحركة المستندة إلى المحاكاة ، والتي ستقبل وسيطة Simulation .

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

على سبيل المثال ، ضع في اعتبارك كيف ، إذا كان var anim = AnimationController.use (السياق ، المدة: widget.duration ؟؟ _duration) ؛ مواطنًا من الدرجة الأولى ، فعليًا لا تحتاج أي من هذه الرسوم المتحركة الضمنية أو الصريحة إلى الوجود. إنها تجعلها زائدة عن الحاجة لأنها تم إنشاؤها جميعًا لإدارة المشكلة الأساسية: تكوين شيء ذي حالة (AnimationController) بسهولة في سياق عنصر واجهة مستخدم. أصبح TAB قريبًا جدًا من العبث ، حيث يمكنك فعل الشيء نفسه باستخدام AnimatedBuilder + AnimatorController.use ().

عند قراءة تعليقاتي أعلاه ، يبدو أن هذا هو بالضبط ما فعلته تمامًا باستخدام خطاف الرسوم المتحركة في الربيع. لقد قمت بتغليف المنطق ثم استخدمت AnimatedBuilder. لجعلها ضمنية ، بحيث عندما قمت بتغيير الخاصية كما يفعل المرء في AnimatedContainer ، سيتم تحريكها ، لقد أضفت للتو طريقة didUpdateWidget (تسمى didUpdateHook في flutter_hooks ) إلى قم بتشغيل الرسوم المتحركة من القيمة القديمة إلى القيمة الجديدة.

هل أنا محق في افتراض أن الأمثلة الموجودة في https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful تمثل الآن المشكلات التي يريد الناس حلها؟

أنا بصراحة لست متأكدا.
وأود أن أقول نعم_. لكن هذا يعتمد حقًا على كيفية تفسير هذه الأمثلة.

في هذا الموضوع ، لدينا تاريخ من عدم فهم بعضنا البعض ، لذلك لا يمكنني ضمان عدم حدوث ذلك مرة أخرى.

هذا جزئيًا سبب كره استخدام أمثلة التعليمات البرمجية واقترح استخراج مجموعة من القواعد بدلاً من ذلك.
الأمثلة ذاتية ولديها حلول متعددة ، بعضها قد لا يحل المشكلة الأوسع.

أود أن أقول أيضًا أنه يجب علينا تضمين جميع نماذج التعليمات البرمجية في هذه المشكلة التي تمت مناقشتها ، وأعتقد أن هناك قائمة أعلاه في مكان ما من rousselGit . يمكنني أن أجعل العلاقات العامة تضيفها إلى مستودع local_state لكنها ليست كلها أمثلة على الكود لذا قد لا يتم تجميعها وتشغيلها جميعًا. لكنهم يظهرون المشاكل المحتملة على الأقل.

يمكنني عمل علاقات عامة وإضافتها إلى مستودع local_state

من شأنه أن يكون مفيدا للغاية.

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

لقد أظهرنا فقط ما لا يعاد استخدامه _ليس _ من حيث صلته بـ Flutter.

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

يمكن ببساطة تعريف إعادة الاستخدام على النحو التالي: _أي شيء يمكن لأداة الإنشاء أن تفعله ._

السؤال هو بعض العناصر ذات الحالة التي يمكن أن توجد داخل أي عنصر واجهة مستخدم ، والتي:

  • يغلف دولته الخاصة
  • يمكن إعداد / تفكيك نفسه وفقًا لـ initState / التخلص من المكالمات
  • يمكن أن تتفاعل عندما تتغير التبعيات في القطعة

ويفعل ذلك بطريقة موجزة لطيفة سهلة التحضير وخالية من المتغيرات ، مثل:
AnimationController anim = AnimationController.stateful(duration: widget.duration);
إذا كان هذا يعمل في الحاجيات عديمي الجنسية وذات الحالة. إذا أعيد البناء عند تغيير عنصر واجهة المستخدم ، إذا كان بإمكانه تشغيل init () والتخلص منها () ، فهذا يعني أن لديك فائزًا وأنا متأكد من أن الجميع سيقدر ذلك.

الشيء الرئيسي الذي أعاني منه هو كيفية القيام بذلك بطريقة فعالة. على سبيل المثال ، تأخذ ValueListenableBuilder وسيطة فرعية يمكن استخدامها لتحسين الأداء بشكل قابل للقياس. لا أرى طريقة للقيام بذلك مع نهج الملكية.

أنا متأكد من أن هذه ليست مشكلة. سنفعل هذا بنفس الطريقة التي تعمل بها أدوات XTransition الآن. إذا كانت لدي حالة معقدة ، وأردت أن يكون لدي طفل باهظ الثمن ، فسأقوم فقط بعمل أداة تغليف صغيرة له. تمامًا مثلما قد نصنع:
FadeTransition(opacity: anim, child: someChild)

يمكننا القيام بذلك بنفس السهولة مع أي شيء نريد تقديمه ، عن طريق تمرير "الشيء" في عنصر واجهة مستخدم لإعادة تصييره.
MyThingRenderer(value: thing, child: someChild)

  • هذا لا _ يتطلب _ تداخلًا كما يفعل Builder ، لكنه يدعمه اختياريًا (يمكن أن يكون .child عبارة عن بناء fxn)
  • يحتفظ بإمكانية استخدامه مباشرة بدون أداة التفاف
  • يمكننا دائمًا إنشاء منشئ واستخدام هذه البنية داخل المنشئ لإبقائه أكثر نظافة. كما أنه يفتح الباب أمام أنواع متعددة من الباني ، مبنية حول نفس الكائن الأساسي ، والتي لا تتضمن نسخ الكود الذي تم لصقه في كل مكان.

متفق عليه معesDotDev. كما ذكرت سابقًا ، فإن العنوان البديل لهذا سيكون "Syntax sugar for Builders".

الشيء الرئيسي الذي أعاني منه هو كيفية القيام بذلك بطريقة فعالة. على سبيل المثال ، تأخذ ValueListenableBuilder وسيطة فرعية يمكن استخدامها لتحسين الأداء بشكل قابل للقياس. لا أرى طريقة للقيام بذلك مع نهج الملكية.

أنا متأكد من أن هذه ليست مشكلة. سنفعل هذا بنفس الطريقة التي تعمل بها أدوات XTransition الآن. إذا كانت لدي حالة معقدة ، وأردت أن يكون لدي طفل باهظ الثمن ، فسأقوم فقط بعمل أداة تغليف صغيرة له. تمامًا مثلما قد نصنع:

لا حاجة لذلك.
تتمثل إحدى مزايا هذه الميزة في أنه يمكن أن يكون لدينا منطق حالة وهو "تخزين نسخة عنصر واجهة المستخدم مؤقتًا إذا لم تتغير معلماتها".

باستخدام الخطافات ، سيكون ذلك useMemo في React:

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

باستخدام هذا الرمز ، سيعيد إنشاء myWidget _only_ عندما يتغير value . حتى إذا كان عنصر واجهة المستخدم الذي يستدعي useMemo يعيد بناء لأسباب أخرى.

هذا مشابه لمُنشئ ثابت للأدوات ، لكنه يسمح بالمعلمات الديناميكية.

هناك مثال على ذلك في الريبو الخاص بتيم.

السؤال هو بعض العناصر ذات الحالة التي يمكن أن توجد داخل أي عنصر واجهة مستخدم ، والتي:

  • يغلف دولته الخاصة
  • يمكن إعداد / تفكيك نفسه وفقًا لـ initState / التخلص من المكالمات
  • يمكن أن تتفاعل عندما تتغير التبعيات في القطعة

أعتقد أنني أجد صعوبة في معرفة السبب من خلال هذه المعلمات ، لا يؤدي StatefulWidget المهمة بشكل أفضل مما يفعله. ولهذا طرحت السؤال حول ما نسعى إليه حقًا هنا في الحل. باعتباري شخصًا يستخدم flutter_hooks أجدهم أكثر متعة في العمل مع StatefulWidget ، لكن هذا فقط لتجنب الإسهاب - ليس لأنني أفكر من حيث الخطافات. أجد في الواقع المنطق حول تحديثات واجهة المستخدم أمرًا صعبًا مع الخطافات مقارنة بـ Widget s.

  • يمكن أن تتفاعل عندما تتغير التبعيات في القطعة

تقصد التبعية التي تم إنشاؤها / الحصول عليها داخل القطعة؟ أو تبعية أقل بكثير من القطعة في الشجرة؟

أنا لا أنكر أن هناك مشكلة تسبب الإسهاب / الارتباك في Flutter ، أنا فقط متردد في الاعتماد على كل شخص لديه في الواقع نفس النموذج العقلي لما هو "إعادة الاستخدام". أنا ممتن جدًا للتوضيح. وعندما يكون لدى الأشخاص نماذج مختلفة ، فإنهم يبتكرون حلولًا مختلفة.

لأن استخدام SW للقيام بذلك أمر جيد لحالة استخدام معينة ، ولكنه ليس جيدًا لاستخلاص المنطق القابل لإعادة الاستخدام لحالة الاستخدام عبر العديد من SW. خذ الإعداد / teardown للرسوم المتحركة كمثال. هذا ليس SW بحد ذاته ، إنه شيء نريد استخدامه عبرهم. بدون دعم من الدرجة الأولى لمشاركة الحالة المغلفة ، ينتهي بك الأمر إلى إنشاء مُنشئ ، مثل TweenAnimationBuilder ، أو إنشاء الكثير من عناصر واجهة المستخدم المحددة ، مثل AnimatedContainer وما إلى ذلك. بالطريقة التي تريدها داخل شجرة.

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

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

باستخدام هذا الرمز ، سيعيد إنشاء myWidget _only_ عندما يتغير value . حتى إذا كان عنصر واجهة المستخدم الذي يستدعي useMemo يعيد بناء لأسباب أخرى.

آه أرى أن Memoized ترجع عنصر واجهة المستخدم نفسه ، ثم تقوم بتمرير [القيمة] كمحفز إعادة البناء ، أنيق!

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

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

فيما يتعلق بأداء تحديد الخطافات داخل طريقة البناء / التصيير (التي أعتقد أن شخصًا ما ذكرها سابقًا في هذه المسألة) ، كنت أقرأ من خلال مستندات React ورأيت هذه الأسئلة الشائعة ، قد تكون مفيدة. يسأل بشكل أساسي عما إذا كانت الخطافات بطيئة بسبب إنشاء وظائف في كل تصيير ، ويقولون لا لأسباب قليلة ، أحدها هو القدرة على حفظ الوظائف باستخدام خطاف مثل useMemo أو useCallback .

https://reactjs.org/docs/hooks-faq.html#are -hooks-slow-because-of-create-functions-in-render

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

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

أنا موافق. لست على دراية جيدة بالأجزاء الداخلية من Flutter ولا على الأجزاء الداخلية للخطاف ، لكنك محق في أن الخطافات ستحتاج (إذا لم تكن موجودة بالفعل) لمعرفة متى يجب أن تعمل مقابل لا ، ويجب ألا يتراجع الأداء.

إنه الفرق بين تشغيل أي رمز على الإطلاق وعدم تشغيل أي رمز على الإطلاق ، وهذا هو مفتاح الأداء الذي يعرضه Flutter في الحالات المثلى اليوم

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

نظرًا لأنه تتم مناقشتها حول حلول لهذه المشكلة في مكان ما في هذا الموضوع ، فأنا أصفها على أنها اقتراح أيضًا.

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

إنها IMO أكثر قابلية للقراءة. في الوقت الحالي ، يتعين عليك الإعلان عن فئتين باستخدام عنصر واجهة مستخدم ذي الحالة مقابل الخطافات حيث تسقط في الاستخدام.

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

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

أعتقد أن هناك بالتأكيد فرصة لتحسين اصطلاحات التسمية وجعلها أكثر رفرفة مثل.

أشياء مثل UseMemoized و UseEffect تبدو غريبة تمامًا ، ويبدو أننا نريد طريقة ما لعدم الاضطرار إلى تشغيل التعليمات البرمجية init () في بناء fxn.

التهيئة حاليًا باستخدام الخطافات مثل هذا (أعتقد؟):

Widget build(){
   useEffect(
      (){
          // Do init stuff
         return (){  //Do dispose stuff };
      }, [ ] ) //<-- pass an empty list of rebuild triggers, so this can only fire once. Passing null here would let it fire every time.
   );
}

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

قد يكون من الأفضل تسمية أشياء مثل useMemoized و useEffect بشكل أكثر وضوحًا hook ComputedValue() و hook SideEffect()

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   hook SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

يعجبني ذلك ، ولست متأكدًا من شعوري حيال استخدام الكلمة الرئيسية hook رغم ذلك ، ولا أعتقد أنها تحل مشكلة المفاهيم الأجنبية. لا يبدو أن إدخال كلمات رئيسية جديدة هو أفضل طريقة في ذهني ، withSideEffect أو withComputedValue ؟ أنا لست مصمم لغة لذا فكلماتي لا قيمة لها.

أشعر أن الوظيفة الشبيهة بالخطاف في الرفرفة ستساعد بشكل كبير في تسهيل منحنى التعلم لمطوري React ، وهو حقًا الجمهور المستهدف عندما تتخذ الشركات القرار بين ReactNative و Flutter.

بترديدlemusthelroy ، يعد

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

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

أعتقد أنه إذا قمت بتأطير المشكلة من حيث الأساسيات ، فمن المرجح أن تصمم حلاً يعمل جيدًا في بقية المكتبة.

بالنسبة للكلمة الرئيسية hook من حيث الأساسيات ؛ يمكن للمرء أن ينظر إليها على أنها إعلان وتعريف دالة من نوع ما من القوالب (ماكرو) ، والبادئة hook تستدعي فقط أن الوظيفة المضمنة لها حالة داخلية (ستاتيكا على غرار c. )

أتساءل عما إذا لم يكن هناك نوع من الفن المسبق في Swift FunctionBuilders.

بينما نحلم ، سأوضح تخميني لما سيكون الرمز الضروري:

Hook SideEffect(void Function() execute, List<Object> dependencies) {
  // Whatever happens each build.
}

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

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

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

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

لست متأكدًا مما تريد قوله بهذا. أسلوب الخطاف الذي أستخدمه أيضًا مع get_it_mixin الخاص بي يجعل قراءة شجرة عناصر واجهة المستخدم أسهل من قراءة منشئ.

مقال مثير للاهتمام حول

@ nt4f04uNd تمت مناقشة جميع نقاطك مسبقًا ، بما في ذلك الأداء ، ولماذا يجب أن تكون ميزة أساسية ،

أقترح عليك قراءة المحادثة بأكملها لفهم النقاط المختلفة.

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

على الرغم من أن هذا قد يكون صحيحًا ، إلا أن هذه المشكلة توضح أن هناك مشكلات لا يمكن حلها باستخدام الأدوات كما هي حاليًا ، لذلك إذا أردنا حل المشكلات ، فليس لدينا خيار سوى صنع شيء جديد. هذا هو نفس مفهوم وجود Future s وإدخال بناء جملة async/await لاحقًا ، وهذا الأخير يجعل الأمور ممكنة ببساطة لم تكن ممكنة بدون بناء جملة جديد.

ومع ذلك ، يقترح الأشخاص أن نجعله جزءًا من إطار العمل. لا يمكن لـ React إضافة صيغة جديدة إلى Javascript لأنها ليست الإطار الوحيد المتاح (حسنًا ، يمكن ذلك من خلال تحويلات Babel) ، ولكن Dart مصمم خصيصًا للعمل مع Flutter (Dart 2 على الأقل ، وليس الإصدار الأصلي) لذلك لدينا المزيد من القدرة على جعل الخطافات تعمل مع اللغة الأساسية. React ، على سبيل المثال ، يحتاج Babel لـ JSX ، ويجب أن يستخدم linter لأخطاء useEffect ، بينما يمكننا جعله خطأ وقت تجميع. إن امتلاك حزمة يجعل التبني أكثر صعوبة ، حيث يمكنك أن تتخيل الجر الذي كانت (لا) تحصل عليه React hooks لو كانت حزمة تابعة لجهة خارجية.

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

لا بد لي من إنشاء عناصر واجهة مستخدم ذات حالة جيدة لاستخدام طريقة init فقط.

ليس عليك فعل ذلك في الواقع ، فإن useEffect () قادرة على تنفيذ initCall داخل البناء. لا تبذل المستندات أي جهد على الإطلاق لشرح هذا الأمر ، وتفترض أساسًا أنك مطور React الذي يعرف بالفعل كيفية عمل الخطافات.

كنت أستخدم هذه الطريقة ولكن كان لدي بعض المشاكل الأخرى المتعلقة بقيود الحزمة ولا أتذكر بالضبط ما كانت.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات