Redux: نهج بديل للإجراءات غير المتزامنة

تم إنشاؤها على ٢٧ ديسمبر ٢٠١٥  ·  44تعليقات  ·  مصدر: reduxjs/redux

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

لتوضيح نهجي ، قمت بتغيير المثال غير المتزامن في استنساخ redux الخاص بي: https://github.com/winstonewert/redux/tree/master/examples/async

عادةً ما تتم الإجراءات الخارجية بجعل منشئ الإجراء غير متزامن. في حالة المثال غير المتزامن ، يرسل منشئ الإجراء fetchPosts إجراء REQUEST_POSTS للإشارة إلى بداية الطلب ، متبوعًا بـ RECEIVE_POSTS بمجرد عودة المنشورات من api.

في المثال الخاص بي ، كل منشئوا الإجراء متزامنون. بدلاً من ذلك ، هناك وظيفة تُرجع قائمة الإجراءات غير المتزامنة التي يجب أن تحدث حاليًا بناءً على الحالة. انظر المثال الخاص بي هنا: https://github.com/rackt/redux/compare/master...winstonewert : master # diff-8a94dc7aa7bdc6e5390c9216a69761f8R12

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

إذن ما الفرق؟

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

أي أفكار؟

discussion feedback wanted

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

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

مشكلة الآثار الجانبية في العمل المبدعين

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

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

بينما لا يزال الناس يبنون أشياء رائعة باستخدام ميزة الإعادة وهي خطوة كبيرة للأمام وبطريقة أبسط وأكثر واقعية من معظم البدائل ، هناك بعض المشكلات التي أراها مع وجود آثار جانبية داخل صانعي الإجراءات:

  • الحالة الضمنية مخفية
  • ازدواجية منطق الأعمال
  • تقلل افتراضات السياق و / أو التبعيات من إمكانية إعادة الاستخدام
  • يصعب اختبار صانعي الأعمال الذين يعانون من آثار جانبية
  • لا يمكن تحسينها أو تجميعها

الحالة الضمنية مخفية

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

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

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

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

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

ازدواجية منطق الأعمال

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

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

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

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

أحد البدائل الممكنة لتوفير الحالة الحالية بشكل صريح هو استخدام redux-thunk وإرجاع رد اتصال للوصول إلى الحالة الحالية. يتيح لك ذلك تجنب تعديل جميع إجراءات الأماكن التي يتم إنشاؤها لتوفير القيمة الحالية ، ولكنه يتطلب الآن من مُنشئ الإجراء معرفة مكان تخزين القيمة في التطبيق العالمي وهذا يحد من القدرة على إعادة استخدام نفس العداد عدة مرات داخل نفس التطبيق أو في تطبيقات مختلفة حيث يمكن تنظيم الدولة بشكل مختلف.

تقلل افتراضات السياق و / أو التبعيات من إمكانية إعادة الاستخدام

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

تمت مناقشة هذا من قبل كيفية إنشاء قائمة عامة كمخفض ومُحسِّن للمكوِّن؟

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

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

بينما يبدو مثال React Elmish مثيرًا للإعجاب ، إلا أنه مفقود بشكل ملحوظ من المثال ، وهما منشئو الإجراء المشكلان ، incrementIfOdd و incrementAsync.

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

يرسل incrementAsync في النهاية مباشرةً إجراء زيادة لا يتم عرضه للمكون الأصلي وبالتالي لا يمكن تغليفه بسياق إضافي.

على الرغم من أن اقتراحك لا يعالج هذه المشكلة بشكل مباشر ، إذا تم تنفيذ incrementAsync كإجراء بسيط أدى إلى تغيير الحالة إلى {counter: 0, incrementAfterDelay: 1000} لتشغيل التأثير الجانبي في مستمع المتجر ، فإن incrementAsync تصبح رسالة بسيطة. إن incrementIfOdd نقي ، لذا يمكن تنفيذه في المخفض أو يمكن توفير الحالة بشكل صريح .... وهكذا يصبح من الممكن تطبيق هندسة الدردار مرة أخرى إذا رغبت في ذلك.

يصعب اختبار صانعي الأعمال الذين يعانون من آثار جانبية

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

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

لا يمكن تحسينها أو تجميعها

ناقش منشور مدونة حديث من John A De Goes مشكلة أنواع البيانات غير الشفافة مثل IO أو Task للتعبير عن التأثيرات. باستخدام الأوصاف التعريفية للآثار الجانبية بدلاً من الأنواع غير الشفافة ، يمكنك تحسين التأثيرات أو دمجها لاحقًا.

هندسة معمارية حديثة لـ FP

تعتبر Thunks والوعود والمولدات غير شفافة ، وبالتالي يجب التعامل مع التحسينات مثل التجميع و / أو قمع مكالمات api المكررة بشكل صريح بوظائف مشابهة لـ fetchPostsIfNeeded .

يزيل اقتراحك fetchPostsIfNeeded ويبدو أنه من الممكن تمامًا تنفيذ وظيفة reactions التي يمكنها تحسين الطلبات المتعددة و / أو استخدام مجموعة مختلفة من واجهات برمجة التطبيقات حسب الحاجة عند طلب بيانات أكثر أو أقل.

التنفيذ الخاص بي

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

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

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

ملخص

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

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

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

ال 44 كومينتر

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

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

مشكلة الآثار الجانبية في العمل المبدعين

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

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

بينما لا يزال الناس يبنون أشياء رائعة باستخدام ميزة الإعادة وهي خطوة كبيرة للأمام وبطريقة أبسط وأكثر واقعية من معظم البدائل ، هناك بعض المشكلات التي أراها مع وجود آثار جانبية داخل صانعي الإجراءات:

  • الحالة الضمنية مخفية
  • ازدواجية منطق الأعمال
  • تقلل افتراضات السياق و / أو التبعيات من إمكانية إعادة الاستخدام
  • يصعب اختبار صانعي الأعمال الذين يعانون من آثار جانبية
  • لا يمكن تحسينها أو تجميعها

الحالة الضمنية مخفية

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

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

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

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

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

ازدواجية منطق الأعمال

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

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

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

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

أحد البدائل الممكنة لتوفير الحالة الحالية بشكل صريح هو استخدام redux-thunk وإرجاع رد اتصال للوصول إلى الحالة الحالية. يتيح لك ذلك تجنب تعديل جميع إجراءات الأماكن التي يتم إنشاؤها لتوفير القيمة الحالية ، ولكنه يتطلب الآن من مُنشئ الإجراء معرفة مكان تخزين القيمة في التطبيق العالمي وهذا يحد من القدرة على إعادة استخدام نفس العداد عدة مرات داخل نفس التطبيق أو في تطبيقات مختلفة حيث يمكن تنظيم الدولة بشكل مختلف.

تقلل افتراضات السياق و / أو التبعيات من إمكانية إعادة الاستخدام

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

تمت مناقشة هذا من قبل كيفية إنشاء قائمة عامة كمخفض ومُحسِّن للمكوِّن؟

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

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

بينما يبدو مثال React Elmish مثيرًا للإعجاب ، إلا أنه مفقود بشكل ملحوظ من المثال ، وهما منشئو الإجراء المشكلان ، incrementIfOdd و incrementAsync.

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

يرسل incrementAsync في النهاية مباشرةً إجراء زيادة لا يتم عرضه للمكون الأصلي وبالتالي لا يمكن تغليفه بسياق إضافي.

على الرغم من أن اقتراحك لا يعالج هذه المشكلة بشكل مباشر ، إذا تم تنفيذ incrementAsync كإجراء بسيط أدى إلى تغيير الحالة إلى {counter: 0, incrementAfterDelay: 1000} لتشغيل التأثير الجانبي في مستمع المتجر ، فإن incrementAsync تصبح رسالة بسيطة. إن incrementIfOdd نقي ، لذا يمكن تنفيذه في المخفض أو يمكن توفير الحالة بشكل صريح .... وهكذا يصبح من الممكن تطبيق هندسة الدردار مرة أخرى إذا رغبت في ذلك.

يصعب اختبار صانعي الأعمال الذين يعانون من آثار جانبية

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

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

لا يمكن تحسينها أو تجميعها

ناقش منشور مدونة حديث من John A De Goes مشكلة أنواع البيانات غير الشفافة مثل IO أو Task للتعبير عن التأثيرات. باستخدام الأوصاف التعريفية للآثار الجانبية بدلاً من الأنواع غير الشفافة ، يمكنك تحسين التأثيرات أو دمجها لاحقًا.

هندسة معمارية حديثة لـ FP

تعتبر Thunks والوعود والمولدات غير شفافة ، وبالتالي يجب التعامل مع التحسينات مثل التجميع و / أو قمع مكالمات api المكررة بشكل صريح بوظائف مشابهة لـ fetchPostsIfNeeded .

يزيل اقتراحك fetchPostsIfNeeded ويبدو أنه من الممكن تمامًا تنفيذ وظيفة reactions التي يمكنها تحسين الطلبات المتعددة و / أو استخدام مجموعة مختلفة من واجهات برمجة التطبيقات حسب الحاجة عند طلب بيانات أكثر أو أقل.

التنفيذ الخاص بي

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

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

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

ملخص

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

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

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

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

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

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

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

أحاول أن أفهم الفرق بين هذا النهج و redux-saga

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

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

في الأساس ، يركز أسلوبي على الوظائف البحتة والحفاظ على كل شيء في حالة إعادة. يؤكد نهج redux-saga على أنه أكثر تعبيرًا. أعتقد أن هناك إيجابيات وسلبيات ، لكني أفضل مني. لكني متحيز.

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

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

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

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

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

أفترض أنك تقصد الطريقة التي تتعامل بها الدالة doReactions () مع بداية / إيقاف XMLHttpRequest؟

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

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

حسنًا ... أعتقد أننا قد لا نعني نفس الشيء بمنطق المجال.

بالطريقة التي أراها ، تغلف وظيفة التفاعلات () منطق المجال ، وهي منفصلة عن وظيفة doReactions () التي تتعامل مع منطق كيفية تطبيق التفاعلات. لكن يبدو أنك تعني شيئًا مختلفًا ...

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

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

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

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

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

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

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

حسنًا ... أعتقد أننا قد لا نعني نفس الشيء بمنطق المجال.

بالطريقة التي أراها ، تغلف وظيفة التفاعلات () منطق المجال ، وهي منفصلة عن وظيفة doReactions () التي تتعامل مع منطق كيفية تطبيق التفاعلات. لكن يبدو أنك تعني شيئًا مختلفًا ...

لقد أخذت نوعًا ما الوحدة النمطية /reactions/index بأكملها ككل ، لكن نعم ، أوافق على أن وظيفة reactions هي منطق مجال بحت. ولكن بدلاً من أن تكون في وحدة خاصة بالمجال ، يتم تغليفها مع النموذج المعياري doReactions . هذا لا يطرق منهجيتك ، بل يجعل من الصعب فهم الفصل بين كود المكتبة ورمز التطبيق بلمحة.

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

هذا لا يقرع طريقتك ؛ أجد هذا النهج جذابًا حقًا.

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

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

أعتقد أنني سأكون أقل قلقًا بشأن حالة الملحمة المخفية إذا كانت الملاحم
استجاب فقط للأحداث التي تم إنشاؤها بواسطة التطبيق (ردود الفعل) بدلاً من المستخدم
بدأت الأحداث (الإجراءات) لأن هذا سيسمح لمخفض التطبيق باستخدام
الحالة الحالية وأي منطق عمل مشروط لتحديد ما إذا كان
يجب أن يسمح التطبيق بالتأثير الجانبي المطلوب دون تكرار
منطق الأعمال.
يوم الإثنين 4 يناير 2016 الساعة 5:56 مساءً. Winston Ewert [email protected]
كتب:

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

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

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

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

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/rackt/redux/issues/1182#issuecomment -168858051.

هذا لا يطرق منهجيتك ، بل يجعل من الصعب فهم الفصل بين كود المكتبة ورمز التطبيق بلمحة.

هذا عادل تمامًا.

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

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

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

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

أجد أن هذا النهج مثير للاهتمام حقًا أيضًا. هل فكرت في استخدام عدة doReactions ، والتي تأخذ تعيينات حالة مختلفة؟ أعتقد أنه سيكون مشابهًا لـ Cyclejs ، حيث يمكنك إنشاء برامج تشغيل قابلة لإعادة الاستخدام:

function main(action$) {
  const state$ = action$.startWith(INITIAL_STATE).scan(reducer);

  return { 
    DOM: state$.map(describeDOM),
    HTTP: state$.map(describeRequests),
    ...
  };
}

يتمثل أحد الاختلافات في أنك لا تستعلم عن محركات الأحداث للحصول على تدفق الإجراءات ( const someEvent$ = sources.DOM.select('.class').events('click') ) ، ولكن حدد الإجراءات في الحوض مباشرة ( <button onClick={() => dispatch(action())} /> ) كما فعلت لطلبات HTTP كذلك.

أعتقد أن تشبيه React يعمل بشكل جيد. على الرغم من ذلك ، لن أعتبر أن DOM هو الحالة الداخلية ، بل واجهة برمجة التطبيقات التي تعمل معها ، بينما تتكون الحالة الداخلية من مثيلات المكون و dom الظاهري.

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

// usage
const describe = (state, dispatch) => <MyComponent state={state} dispatch={dispatch} />;
const driver = createReactDOMDriver({ container } /* opts */);
store.subscribe(() => driver.update(describe(store.getState(), store.dispatch)); 
// (could be simplified further to eg. `store.use(driver, describe)` )

// implementation
const createReactDOMDriver = ({ container }) => {
  return {
    update: (element) => ReactDOM.render(element, container),
    destroy: () => ReactDOM.unmountComponentAtNode(container),
  };
};

سأحصل على describe أخذ getState (بدلاً من لقطة الحالة) و dispatch . بهذه الطريقة ، يمكن أن تكون غير متزامنة كما تريد.

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

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

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

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

نقطة عادلة. لم أفكر جيدًا في التعيين بين فكرتك describe كانت الوظيفة reactions ، لكن هذا قد لا يكون صحيحًا.

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

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

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

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

الوصف المفترض هو وظيفة التفاعلات ، لكن هذا قد لا يكون صحيحًا.

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

winstonewert إنه موضوع طويل وليس لدي وقت لقراءته الآن أو التحقق من الكود ولكن ربما يستطيع yelouafi الإجابة عليك.

نشأ مشروع redux-saga من مناقشات طويلة هنا

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

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

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

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

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

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

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

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

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

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

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

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

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

آمل ألا يكون أي شيء أقوله قد ظهر على أنه هجوم على ملحمة الإعادة

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

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

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

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

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

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

أنا لا أرى ما أنت عليه بعد.

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

أنا موافق. إذا كنت تكتب باليد آلة دولة معقدة لدينا مشكلة. (في الواقع سيكون أمرًا رائعًا إذا تمكنا من تحويل المولد إلى مخفض).

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

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

winstonewert لا أنا لا آخذ أي شيء كهجوم. لم يكن لدي الوقت الكافي للنظر في التعليمات البرمجية الخاصة بك حقًا :)

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

لا لست كذلك ، متجر redux هو إسقاط ، لكن الملحمة عبارة عن مستمع بسيط قديم.

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

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

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

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

لا ، ليس القضاء عليه ، فقط جعله مصدر قلق للتنفيذ السري ، لمعظم الأغراض.

يبدو لي أن هناك إجماعًا قويًا حقًا في مجتمع Redux على أن تخزين حالة المجال في المتجر يعد فوزًا كبيرًا (وإلا ، فلماذا تستخدم Redux على الإطلاق؟). أقل إلى حد ما هو الإجماع على أن تخزين حالة واجهة المستخدم هو فوز ، بدلاً من تغليفها في مكونات. ثم هناك فكرة مزامنة حالة المتصفح في المتجر ، مثل عنوان URL (redux-simple-router) أو بيانات النموذج. ولكن يبدو أن هذا هو الحد النهائي لتخزين حالة / مرحلة العملية طويلة الأمد في المتجر.

عذرًا إذا كان هذا مماسًا ، لكنني أعتقد أن النهج العام للغاية مع قابلية الاستخدام الجيدة للمطورين يجب أن يحتوي على الميزات التالية:

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

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

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

ربما أكون قد خرجت عن مسارها الآن ، لذا سأترك الأمر عند هذا الحد :)

acjay أعتقد أننا نتفق معك في هذه النقاط ، المشكلة هي العثور على هذا التطبيق الذي يحل كل هؤلاء بشكل صحيح :)

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

لست متأكدًا ، ولكن هذا قد يمنع الملاحم ذات النمط while(true) { ... } . هل الحلقات ستكون مجرد نتيجة لتطور الدولة؟

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

كما أوضحت في (https://github.com/yelouafi/redux-saga/issues/22#issuecomment-168872101) السفر عبر الزمن بمفرده (أي بدون إعادة التحميل الساخن) ممكن للقصص. كل ما تحتاجه لنقل ملحمة إلى نقطة معينة هو تسلسل التأثيرات الناتجة من البداية إلى تلك النقطة ، بالإضافة إلى نتائجها (الحل أو الرفض). ثم ستقود المولد بهذا التسلسل

في الفرع الرئيسي الفعلي (لم يتم إصداره بعد في npm). تدعم Sagas المراقبة ، وترسل جميع التأثيرات الناتجة ، بالإضافة إلى نتائجها كإجراءات إلى المتجر ؛ كما أنها توفر معلومات هرمية لتتبع الرسم البياني لتدفق التحكم.

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

في أمثلة الريبو ، يوجد مثال على مراقب الملحمة (تم تنفيذه كبرنامج وسيط Redux). يستمع إلى سجل التأثير ويحافظ على هيكل الشجرة الداخلي (جيد البناء كسول). يمكنك طباعة أثر التدفق بإرسال إجراء {type: 'LOG_EFFECT'} إلى المتجر

فيما يلي لقطة لسجل التأثير من المثال غير المتزامن

saga-log-async

تحرير: آسف رابط الصورة الثابتة

مثيرة للاهتمام! وهذه الصورة لأدوات التطوير _ رائعة_.

هذا بارد :)

في الواقع ، شاشة العرض هذه رائعة جدًا.

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

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

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

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

حسنًا ، لقد جمعت المزيد من المكتبة وقمت بنقل المثال الواقعي لاستخدامها:

أولاً ، لدينا تنفيذ ردود الفعل:
https://github.com/winstonewert/redux-reactions/blob/master/src/index.js
تتكون الواجهة من ثلاث وظائف: يأخذ startReactions المتجر ، ووظيفة ردود الفعل ، والتعيين من الأسماء إلى السائقين. ينشئ كل من fromEmitter و fromPromiseFactory برامج تشغيل.

هنا يستدعي المثال startReactions لتمكين النظام:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/store/configureStore.dev.js#L28

التكوين الأساسي للتفاعلات هنا:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/index.js.
في الواقع ، تتكرر وظيفة التفاعلات فقط من خلال المكونات التي تتفاعل مع جهاز التوجيه الذي يبحث عن تلك التي تحتوي على وظيفة التفاعلات () لمعرفة التفاعلات الفعلية المطلوبة لتلك الصفحة.

يتوفر تنفيذ نوع تفاعل github api هنا: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js. هذا هو في الغالب نسخ / لصق من البرامج الوسيطة المستخدمة في المثال الأصلي. النقطة الحرجة هنا: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js#L79 ، حيث تستخدم fromPromiseFactory لإنشاء برنامج التشغيل من وظيفة يعود بالوعود.

راجع وظيفة التفاعلات الخاصة بالمكون هنا: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/containers/RepoPage.js#L80.

منشئو التفاعل والمنطق المشترك موجودون في https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/data.js

مرحبا يا رفاق! تم نشر أداة تحسين المتجر التي تم نشرها للتو والتي تتيح لك استخدام نظام تأثيرات يشبه هندسة Elm أيضًا! آمل أن نكون قادرين على التعلم وتحسين كل هذه الأساليب للمضي قدمًا لتلبية جميع احتياجات المجتمع: ابتسم:

https://github.com/raisemarketplace/redux-loop

قد يرغب أي شخص مهتم بالمناقشة في رؤية مزيد من المناقشة حول فكرتي هنا: https://github.com/winstonewert/redux-reactions/issues/7

يمكنك أيضًا إلقاء نظرة على أحد الفروع هنا ، حيث أقوم بإعادة صياغة تطبيق العداد ليكون أكثر إلمامًا باستخدام نمطي:
https://github.com/winstonewert/redux-reactions/tree/elmish/examples/counter

اكتشفت أيضًا أنني أقوم بإعادة اختراع النهج المستخدم هنا: https://github.com/ccorcos/elmish

مرحبًا yelouafi ، هل يمكنك إعادة نشر رابط فكرة مراقبة الملحمة؟ هذه حقا بعض الاشياء العظيمة يبدو أن الرابط ميت (404). أود أن أرى المزيد!

مناقشة جديدة ذات صلة: https://github.com/reactjs/redux/issues/1528

(أعتقد أن هذا مرتبط. آسف إذا كان هذا مكانًا خاطئًا)

هل يمكننا التعامل مع جميع التأثيرات بنفس طريقة عرض DOM؟

  1. jQuery هو محرك DOM بواجهة حتمية. React هو محرك DOM بواجهة تعريفية. لذلك ، بدلاً من طلب: "تعطيل هذا الزر" ، نعلن: "نحتاج إلى تعطيل هذا الزر" ويقرر السائق ما يجب القيام به معالجات DOM. بدلاً من طلب: " GET \product\123 " ، نعلن: "نحتاج إلى تلك البيانات" ويقرر السائق طلبات الإرسال / الإلغاء.
  2. نحن نستخدم مكونات React مثل API إلى محرك DOM. دعنا نستخدمها للتفاعل مع برامج تشغيل أخرى أيضًا.

    • <button ...> - نبني طبقة العرض الخاصة بنا على مكونات التفاعل "العادية"

    • <Map ...> - نستخدم مكونات "مجمعة" لتحويل الواجهة الإلزامية لبعض المكتبات إلى واجهة تعريفية. نحن نستخدمها بنفس طريقة المكونات "العادية" ، ولكن داخليًا هي في الواقع محركات.

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

    • <Http url={'/product/'+props.selectedProductId} onSuccess={props.PRODUCT_LOADED} /> (أو "ذكي" <Service...> ) - نحن نبني مكونات برنامج تشغيل طبقة الخدمة (UI-less)

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

لست متأكدًا من ملاءمة new Date أو Math.random هنا.

هل من الممكن دائمًا تحويل واجهة برمجة التطبيقات (API) الضرورية إلى واجهة برمجة التطبيقات (API) التصريحية؟
هل تعتقد أن هذه رؤية قابلة للتطبيق على الإطلاق؟

شكرا

نظرًا لأن لدينا الملاحم والأدوات الرائعة الأخرى للإجراءات غير المتزامنة ، أعتقد أنه يمكننا إغلاق هذا بأمان الآن. تحقق من # 1528 للحصول على بعض الاتجاهات الجديدة المثيرة للاهتمام (أبعد من مجرد التزامن).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات