Redux: قلق على الدمج المخفض ، عندما يتعلق الإجراء بمخفضات متعددة

تم إنشاؤها على ٢١ أغسطس ٢٠١٥  ·  52تعليقات  ·  مصدر: reduxjs/redux

أنا أعمل على تطبيق دردشة تم إنشاؤه باستخدام React.js و Flux. عندما قرأت مستندات Redux ، تبدو الوظيفة combineReducer غريبة بالنسبة لي. فمثلا:

هناك ثلاثة متاجر تسمى messageStore ، unreadStore ، threadStore . وهناك إجراء يسمى NEW_MESSAGE . سيتم تحديث جميع المتاجر الثلاثة على الرسائل الجديدة. في Redux ، المخازن عبارة عن مخفضات مدمجة بـ combineReducer مثل:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

يبدو لي أنه في مثل هذه الحالات ، فإن تقسيم المخفضات لا يجعل حياتي أسهل. ومع ذلك ، قد يكون المتجر العملاق الذي تم بناؤه باستخدام نظام غير قابل للتغيير حلاً أفضل لهذا. مثل:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

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

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

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

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

في بعض الحالات ، قد يعتمد أحد المخفضات على بيانات من أخرى (مثل نقل رسالة من غير مقروءة إلى رسالة ، أو حتى الترويج لرسالة كسلسلة رسائل جديدة من رسالة إلى سلسلة رسائل)

من أعراض مشكلة. إذا كان عليك نقل الأشياء داخل حالتك كثيرًا ، فربما يحتاج شكل الحالة إلى التطبيع أكثر. بدلاً من { readMessages, unreadMessages } يمكنك الحصول على { messagesById, threadsById, messageIdsByThreadIds } . قراءة الرسالة؟ حسنًا ، قم بتبديل state.messagesById[id].isRead . تريد قراءة الرسائل لموضوع؟ احصل على state.threadsById[threadId].messageIds ، ثم messageIds.map(id => state.messagesById[id]) .

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

ال 52 كومينتر

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

أنظر أيضا:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

للإلهام.

المثال الأول الذي أشرت إليه مشابه لحالتي ، يتم التعامل مع requestType في كلا المخفضين.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

تجعل مخفضات التقسيم من السهل صيانتها عند فصل مخفضين عن الآخرين. ولكن عندما يسلمون نفس الإجراءات ، فإن ذلك يؤدي إلى:

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

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

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

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

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

في بعض الحالات ، قد يعتمد أحد المخفضات على بيانات من أخرى (مثل نقل رسالة من غير مقروءة إلى رسالة ، أو حتى الترويج لرسالة كسلسلة رسائل جديدة من رسالة إلى سلسلة رسائل)

من أعراض مشكلة. إذا كان عليك نقل الأشياء داخل حالتك كثيرًا ، فربما يحتاج شكل الحالة إلى التطبيع أكثر. بدلاً من { readMessages, unreadMessages } يمكنك الحصول على { messagesById, threadsById, messageIdsByThreadIds } . قراءة الرسالة؟ حسنًا ، قم بتبديل state.messagesById[id].isRead . تريد قراءة الرسائل لموضوع؟ احصل على state.threadsById[threadId].messageIds ، ثم messageIds.map(id => state.messagesById[id]) .

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

أود أن محاولة ذلك.

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

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

لقد كتبت مساعدتي combineReducers() الغالب لدعم Immutable.js ، ولكن يمكن أن يتطلب الأمر مخفضات إضافية تعامل مثل مخفضات الجذر:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

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

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

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

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

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

هل يستحق تقديم PR للمخفضات الجزئية ومخفضات الجذر الإضافية؟ هل هذه فكرة مروعة ، أم أنها شيء يمكن أن يكون مفيدًا لعدد كافٍ من الأشخاص الآخرين؟

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

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

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

لقد تمكنت من التغلب على هذه المشكلة باستخدام البرامج الوسيطة التالية:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

بحيث يمكن لكل إجراء الوصول إلى مخزن الجذر عبر action.getState() .

@ rhys-vdw شكرا لذلك! برمجيات وسيطة مفيدة حقًا :)

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

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

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

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

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

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

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

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

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

حسنًا ، أرى بوضوح فوائد الحصول على حالة طبيعية والتعامل مع جزء الإحياء من نوع حالتي مثل DB.

ما زلت أفكر في ما إذا كان أسلوبgaearon (وضع إشارة على الكيانات للحذف) أو @ rhys-vdw أفضل (حل المراجع وإزالة المراجع ذات الصلة في مكانها) .

أي أفكار / تجارب العالم الحقيقي؟

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

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

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

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

قد يؤدي السماح IMHO للمخفضات بالوصول إلى حالة الجذر إلى اقتران إجمالي أقل من الحلول الحالية.

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

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

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

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

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

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

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

jwhiting : هذا أحد الأغراض العديدة لاستخدام "وظائف المحدد" لاستخراج البيانات التي تحتاجها من الحالة ، خاصة في mapStateToProps . يمكنك إخفاء حالة الشكل بحيث يكون هناك حد أدنى من الأماكن التي تحتاج بالفعل إلى معرفة مكان state.some.nested.field . إذا لم تره بعد ، فانتقل إلى مكتبة الأدوات المساعدة Reselect ، وقسم Computing Derived Data في مستندات Redux.

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

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

إن الاحتفاظ بكل الكود في مخفض واحد ليس حقًا فصلًا جيدًا عن
اهتمامات. إذا تم استدعاء المخفض الخاص بك من قبل الجذر ، فيمكنك الاتصال به يدويًا
وتمرير الحالة الجذرية صراحةً كما أعتقد أن اقتراحك ولكن إذا كان لديك
التسلسل الهرمي للمخفض الأصل يحتوي على أجهزة تقليل الاختزال عليك أن تمزقها
خارج. فلماذا لا تجعلها أكثر تعقيدًا بحيث لا تحتاج إلى تمزيقها
في وقت لاحق أو اللجوء إلى thunks كما يبدو أن معظم الناس يفعلون؟
يوم السبت 16 أبريل 2016 الساعة 8:27 مساءً Josh Whiting [email protected]
كتب:

markerikson https://github.com/markerikson gaearon
https://github.com/gaearon هذا منطقي وسأستكشف ذلك ،
شكرا لك. استخدام المحددات في mapStateToProps لأي شيء خارج ذلك
إن الشاغل الأساسي للمكون (أو ربما لجميع وصول الدولة) سيحمي
مكونات من التغييرات في شكل الدولة الأجنبية. أود أن أجد طريقة
لجعل تغليف الحالة أكثر صرامة في مشروعي ،
لذلك فإن الانضباط في الكود ليس هو الحارس الوحيد له والمرحب به
اقتراحات هناك.

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

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

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

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

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

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

ما المشكلة في وجود مخفضات مختلفة تتعامل مع نفس نوع الإجراء؟

لا توجد مشكلة ، إنه نمط شائع ومفيد للغاية. في الواقع ، هناك إجراءات _ السبب_: إذا تم تعيين الإجراءات لمخفضات 1: 1 ، فلن يكون هناك فائدة تذكر في وجود إجراءات على الإطلاق.

@ rhys-vdw حاولت استخدام البرامج الوسيطة التي نشرتها.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

وفي إنشاء متجري لدي

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

اتلقى الخطأ التالي:

applicationMiddleware.js؟ ee15: 49 خطأ في النوع غير معلوم: البرامج الوسيطة ليست دالة (...)
(دالة مجهولة) @ applyMiddleware.js؟ ee15: 49
(دالة مجهولة) @ applyMiddleware.js؟ ee15: 48
createStore @ createStore.js؟ fe4c: 65
configStore @ configStore.js؟ ffde: 45
(وظيفة مجهولة) @ index.jsx؟ fdd7: 25
(دالة مجهولة) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (دالة مجهولة) @ bundle.js: 588
webpack_require @ bundle.js: 556
(دالة مجهولة) @ bundle.js: 579
(دالة مجهولة) @ bundle.js: 582

@ mars76 : على الأرجح أنك لا تستورد شيئًا بشكل صحيح. إذا كان هذا هو المحتوى الكامل لـ customMiddleware.js ، فأنت لا تقوم بتصدير _ أي شيء_. بشكل عام ، تقوم بتمرير أربعة برامج وسيطة إلى applyMiddleware ، ويفترض أن أحدها ليس مرجعًا صالحًا. تراجع ، وقم بإزالة كل منهم ، وأعد إضافة واحدة في كل مرة ، واعرف أيها غير صالح ، واكتشف السبب. تحقق من بيانات الاستيراد والتصدير ، وتحقق جيدًا من الكيفية التي من المفترض أن تقوم بتهيئة الأشياء بها ، وما إلى ذلك.

مرحبا،

شكرا ماركريكسون

لقد قمت بتحويل بناء الجملة على النحو التالي ولا بأس بذلك الآن:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

كيف أحاول الوصول إلى المتجر في Action Creator نفسه (ليس في Reducer). أحتاج إلى إجراء مكالمة Async وأحتاج إلى تمرير أجزاء مختلفة من حالة المتجر تدار بواسطة مخفضات مختلفة.

هذا ما أفعله. لست متأكدا ما إذا كان هذا هو النهج الصحيح.

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

أكثر ما يقلقني هو أن Reducers تستدعي الآن أساليب منشئ الإجراء.

نقدر أي تعليقات.

شكر

@ mars76 : مرة أخرى ، يجب ألا تحاول أبدًا الإرسال من داخل المخفض.

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

شكرا جزيلا Marcerikson.

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

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

tacomanator : لمعلوماتك ، لا يستجيب دان عادةً

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

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

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

كان الاختراق السريع الذي استخدمته لدمج المخفضات في واحد

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

حيث reducerA إلى reducerZ هي وظائف المخفض الفردية.

brianpkelley : نعم ، هذا أساسًا https://github.com/acdlite/reduce-reducers .

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

هيه ، بالتأكيد.

لمعلوماتك ، قد ترغب في قراءة الأسئلة الشائعة: "هل يتعين علي استخدام combineReducers ؟" وهيكلة أقسام سلسلتي التعليمية "عملية إعادة التشغيل" على منشورين يوضحان بعض تقنيات المخفض المتقدمة (انظر الجزء 7 والجزء 8).

سنتي عند استخدام transform .

مثال:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

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

  • الجذر مسترجع

    • تجمع مخفضات التصدير. js مخفضات من كل ولاية

    • middleware.js export applicationMiddleware لجميع البرامج الوسيطة

    • configureStore.js createStore باستخدام (reducers.js، middleware.js)

    • Actions.js تُصدِّر الإجراءات التي ترسل الإجراءات من مجلدات الحالات <== هذا

    • الدولة 1

    • initial.js يحتوي هذا الملف على الحالة الأولية (أستخدم ثابتًا لإنشاء سجل)

    • action-types.js يحتوي هذا الملف على أنواع الإجراءات (أستخدم dacho كمرآة رئيسية)

    • Actions.js يحتوي هذا الملف على إجراءات الدولة

    • reducer.js يحتوي هذا الملف على المخفض الدولة

    • الدولة 2

    • الأولي. js

    • نوع العمل. js

      ....

لماذا ا ؟
1- يوجد نوعان من الإجراءات في كل تطبيق:

  • الدولة الإجراءات (redux / state1 / Actions.js) مسؤولية هذه الإجراءات لتغيير الحالة فقط ، لا يجب عليك إطلاق هذه الإجراءات مباشرةً باستخدام إجراءات التطبيق دائمًا

    • إجراءات التطبيق (redux / Actions.js) هذه الإجراءات مسؤولة عن تنفيذ بعض منطق التطبيق ويمكن أن تطلق إجراءات الحالة

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

ماذا عن الوقت الذي يحتاج فيه مخفضان إلى تغيير نفس الخاصية؟

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

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

ثم ماذا؟

@ Wassap124 لا يمكن لاثنين من المخفضات إدارة نفس الحالة. هل تقصد إجراءين يحتاجان إلى تغيير نفس الخاصية؟

بدون مزيد من التفاصيل قد تفضل استخدام thunk لإرسال إجراء "خطأ محدد".

@ rhys-vdw أنا عالق قليلاً في محاولة تقسيم مخفض طويل إلى حد ما.
وفيما يتعلق باقتراحك ، ألا يتعارض ذلك مع قاعدة عدم وجود آثار جانبية؟

@ Wassap124 ، @ rhys-vdw: للتوضيح ، ليس من الممكن أن يدير مخفضان نفس الحالة _ إذا كنت تستخدم combineReducers _ فقط. ومع ذلك ، يمكنك بالتأكيد كتابة منطق مخفض آخر ليناسب حالة الاستخدام الخاصة بك - راجع https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

إذا قمت ببعض البحث عن مصدر سريع ...

يُرجع combineReducers توقيع دالة مثل ذلك

return function combination(state = {}, action) {

راجع https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

بعد إنشاء الإغلاق للإشارة إلى كائن المخفضات الفعلي.

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

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

قد يكون هذا نمطًا مضادًا وقد لا يكون ، لكن البراغماتية كانت دائمًا أكثر أهمية.

بدلاً من استدعاء مخفض من مخفض آخر (وهو مضاد للنمط ، لأن globalReducerHandler لا علاقة له بالمخفض الذي يطلق عليه) ، استخدم reduceReducers ، وهو أساسًا compose للمخفضات (حسنًا ، إنه تكوين حرفيا في (Action ->) monad).

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

القضايا ذات الصلة

captbaritone picture captbaritone  ·  3تعليقات

vraa picture vraa  ·  3تعليقات

mickeyreiss-visor picture mickeyreiss-visor  ·  3تعليقات

olalonde picture olalonde  ·  3تعليقات

timdorr picture timdorr  ·  3تعليقات