Redux: كيف تقطع النموذج المعياري عند تحديث الكيانات المتداخلة؟

تم إنشاؤها على ٣ نوفمبر ٢٠١٥  ·  32تعليقات  ·  مصدر: reduxjs/redux

لذلك لدي هذا الهيكل المتداخل تحت ولايتي

state = {
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe1'},{title: 'exe2'}]}
   ]
}

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

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

state.plans[planIdx].exercises.push({})

state.plans[planIdx].exercises[exerciseIdx] = exercise

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

case 'UPDATE_PLAN':
    return {
      ...state,
      plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
      ]
    };

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

شكرا لك!

docs question

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

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

لذلك ستبدو دولتك

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

قد تبدو المخفضات الخاصة بك

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

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

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

ثالثًا ، لاحظ أن الكيانات المقلصة ( plans و exercises ) لها بنود خاصة تراقب action.entities . هذا في حالة وجود استجابة خادم بـ "الحقيقة المعروفة" التي نريد تحديث جميع كياناتنا لتعكسها. لإعداد بياناتك بهذه الطريقة قبل إرسال أي إجراء ، يمكنك استخدام normalizr . يمكنك أن ترى أنه مستخدم في مثال "العالم الحقيقي" في Redux repo.

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

أوه ، لقد استخدمت بناء الجملة { ...a, ...b } . تم تمكينه في بابل المرحلة 2 كمقترح ES7. يطلق عليه "عامل انتشار الكائن" ويعادل كتابة Object.assign({}, a, b) .

بالنسبة للمكتبات ، يمكنك استخدام Lodash (احرص على عدم التغيير ، على سبيل المثال merge({}, a, b} صحيح ولكن merge(a, b) ليس كذلك) ، أو دعم ، أو رد فعل ، أو

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

يمكن كتابتها كـ

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

ال 32 كومينتر

يوصى بتطبيع JSON المتداخلة مثل: https://github.com/gaearon/normalizr

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

لذلك ستبدو دولتك

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

قد تبدو المخفضات الخاصة بك

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

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

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

ثالثًا ، لاحظ أن الكيانات المقلصة ( plans و exercises ) لها بنود خاصة تراقب action.entities . هذا في حالة وجود استجابة خادم بـ "الحقيقة المعروفة" التي نريد تحديث جميع كياناتنا لتعكسها. لإعداد بياناتك بهذه الطريقة قبل إرسال أي إجراء ، يمكنك استخدام normalizr . يمكنك أن ترى أنه مستخدم في مثال "العالم الحقيقي" في Redux repo.

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

أوه ، لقد استخدمت بناء الجملة { ...a, ...b } . تم تمكينه في بابل المرحلة 2 كمقترح ES7. يطلق عليه "عامل انتشار الكائن" ويعادل كتابة Object.assign({}, a, b) .

بالنسبة للمكتبات ، يمكنك استخدام Lodash (احرص على عدم التغيير ، على سبيل المثال merge({}, a, b} صحيح ولكن merge(a, b) ليس كذلك) ، أو دعم ، أو رد فعل ، أو

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

يمكن كتابتها كـ

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

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

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

@ andre0799 أو يمكنك فقط استخدام Immutable.js))

من الناحية المثالية ، نريد مثالًا على لوحة كانبان.

لقد كتبت واحدة . ربما يمكنك تفرعها وتعديلها حسب رغبتك.

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

شكرًا gaearon على إجابتك ، شرح رائع!

لذلك ، عندما تفعل CREATE_PLAN يجب أن تنشئ تلقائيًا تمرينًا افتراضيًا وتضيف إليه. كيف أتعامل مع مثل هذه الحالات؟ هل يجب علي استدعاء 3 إجراءات متتالية؟ CREATE_PLAN, CREATE_EXERCISE, ADD_EXERCISE_TO_PLAN من أين أقوم بهذه المكالمات ، إذا كان هذا هو الحال؟

لذلك ، عندما تفعل CREATE_PLAN ، يجب أن تنشئ تلقائيًا تمرينًا افتراضيًا وتضيف إليه. كيف أتعامل مع مثل هذه الحالات؟

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

يمكنك استخدام البرامج الوسيطة Redux Thunk لكتابة منشئ إجراء يستدعي كلاهما:

function createPlan(title) {
  return dispatch => {
    const planId = uuid();
    const exerciseId = uuid();

    dispatch({
      type: 'CREATE_EXERCISE',
      id: exerciseId,
      exercise: {
        id: exerciseId,
        title: 'Default'
      }
    });

    dispatch({
      type: 'CREATE_PLAN',
      id: planId,
      plan: {
        id: planId,
        exercises: [exerciseId],
        title
      }
    });
  };
}

بعد ذلك ، إذا قمت بتطبيق برمجيات Redux Thunk الوسيطة ، فيمكنك تسميتها بشكل طبيعي:

store.dispatch(createPlan(title));

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

كيف يمكنني عرض currentPosts غرار مجموعة المفاتيح currentPlans ؟ هل سأحتاج إلى تعيين كل مفتاح في currentPosts إلى العنصر المقابل له في entities.posts في الوظيفة mapStateToProps ؟ ماذا عن فرز currentPosts ؟

هل كل هذا ينتمي إلى تركيبة مخفض؟

أفتقد شيئًا هنا ...

فيما يتعلق بالسؤال الأصلي ، أعتقد أنه تم إنشاء مساعدي React Immutability Helpers لهذا الغرض

كيف يمكنني عرض المشاركات الحالية على غرار مجموعة مفاتيح CurrentPlans؟ هل سأحتاج إلى تعيين كل مفتاح في currentPosts إلى الكائن المقابل له في members.posts في دالة mapStateToProps؟ ماذا عن فرز المشاركات الحالية؟

هذا صحيح. ستفعل كل شيء عند استرداد البيانات. يرجى الرجوع إلى أمثلة "عربة التسوق" و "العالم الحقيقي" التي تأتي مع Redux repo.

شكرًا ، لقد بدأت بالفعل في الحصول على فكرة بعد قراءة " حساب البيانات المشتقة" في المستندات.

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

ههههههههههه

أي مكوِّن connect() ed يحتوي على dispatch يتم حقنه كخاصية بشكل افتراضي.

this.props.dispatch(createPlan(title));

هذا سؤال استخدام لا علاقة له بهذا الموضوع. من الأفضل الرجوع إلى الأمثلة أو إنشاء أسئلة StackOverflow لهذا الغرض.

أتفق مع دان في تطبيع البيانات وتسطيح هيكل الدولة قدر الإمكان. قد يتم وضعها كوصفة / أفضل ممارسة في الوثائق ، لأنها كانت ستوفر لي بعض الصداع.

نظرًا لخطئي المتمثل في الحصول على القليل من العمق في حالتي ، فقد ارتكبت هذا lib للمساعدة في تعديل الحالة العميقة وإدارتها باستخدام Redux: https://github.com/baptistemanson/immutable-path
ربما أخطأت في كل شيء ولكن سأكون مهتمًا بتعليقاتك. أتمنى أن يساعد شخص ما.

كان هذا مفيدًا. شكرا للجميع.

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

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

على سبيل المثال: يجلب العميل بيانات التمرين المتداخلة -> يقوم العميل بتطبيعها وتخزينها في إعادة -> يقوم المستخدم بإجراء تغييرات على البيانات الموحدة على جانب العميل -> ينقر المستخدم على حفظ -> يحول تطبيق العميل البيانات القياسية المحدثة إلى النموذج المتداخل حتى يتمكن من إرسالها إلى الخادم البعيد -> يرسل العميل إلى الخادم

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

شكر

هناك شيء يسمى https://github.com/gpbl/denormalizr لكنني لست متأكدًا من مدى قربه من تتبع التحديثات العادية. لقد كتبت normalizr في غضون ساعتين للتطبيق الذي عملت عليه ، فنحن نرحب بك لتقسيمه وإضافة إلغاء التطبيع 😄.

رائع بالتأكيد سألقي نظرة على إلغاء التطبيع وأساهم في مشروعك بمجرد أن يكون لدي شيء. عمل رائع لبضع ساعات ؛-) شكرًا على عودتك إلي.

أنا في نفس الموقف مع تغيير البيانات المتداخلة بعمق ، ومع ذلك ، فقد وجدت حلاً عمليًا باستخدام immutable.js.

هل من الجيد أن أقوم بالارتباط بمنشور StackOverflow الذي أطلبه للحصول على تعليقات حول الحل هنا؟

أنا أقوم بربطه الآن ، يرجى حذف المنشور الخاص بي أو تحديد ما إذا كان الارتباط هنا غير مناسب:
http://stackoverflow.com/questions/37171203/manipulation-data-in-nested-arrays-in-redux-with-immutable-js

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

ariofrio : آه ... أنا في حيرة من

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

إليكم ما أعنيه. لنفترض أن الحالة الحالية تبدو كما يلي:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 6]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
      5: {title: 'exe5'}
      6: {title: 'exe6'}
    }
  },
  currentPlans: [1, 2]
}

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

{type: "REMOVE_EXERCISE", payload: 2}

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

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

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

وقد تبدو رسالة إزالة التمرين كما يلي:

{type: "REMOVE_EXERCISE", payload: {plan_index: 0, exercise_index: 1}}

بعض الأفكار:

  • يمكنك الاحتفاظ ببحث عكسي عن التمارين لخطط تبسيط هذا السيناريو. وبالمثل ، فإن ما تفعله مكتبة Redux-ORM هو الإنشاء التلقائي "من خلال الجداول" للعلاقات متعددة الأنواع. لذلك ، في هذه الحالة سيكون لديك "جدول PlanExercise" في متجرك ، والذي قد يحتوي على ثلاثة توائم {id, planId, exerciseId} . بالتأكيد مسح O (n) ، ولكنه مسح مباشر.
  • عملية O (n) ليست أمرًا سيئًا بطبيعتها. يعتمد هذا تمامًا على حجم N ، والعامل الثابت أمام المصطلح ، وعدد مرات حدوثه ، وما الذي يحدث أيضًا في تطبيقك. سيكون تكرار قائمة من 10 أو 15 عنصرًا وإجراء بعض عمليات التحقق من المساواة عند نقرة زر المستخدم شيئًا مختلفًا تمامًا عن تكرار قائمة من 10 ملايين عنصر يدخل إلى النظام كل 500 مللي ثانية وإجراء بعض العمليات المكلفة لكل منها بند. في هذه الحالة ، هناك احتمالات أنه حتى التحقق من آلاف الخطط لن يكون عنق زجاجة ذو مغزى كبير.
  • هل هذا من مخاوف الأداء الفعلي الذي تراه ، أو مجرد التطلع إلى المشكلات النظرية المحتملة؟

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

الحل الخاص بي مثل هذا:

function deepCombinReducer(parentReducer, subReducer) {
    return function (state = parentReducer(void(0) /* get parent reducer initial state */, {}) {
        let finalState = {...state};

        for (var k in subReducer) {
          finalState[k] = subReducer(state[k], action);
        }

       return parentReducer(finalState, action);
    };
}

const parentReducer = function(state = {}, action) {
    return state;
}

const subReducer = function(state = [], action) {
    state = Immutable.fromJS(state).toJS();
    switch(action.type) {
       case 'ADD':
          state.push(action.sub);
           return state;
       default:
          return state;
   }
}

export default combineReducers({
   parent: deepCombinReducer(parentReducer, {
       sub: subReducer
   })
})

بعد ذلك ، يمكنك الحصول على المتجر مثل هذا:

{
    parent: {
       sub: []
    }
}

dispatch({
    type: 'ADD',
    sub: '123'
});

// the store will change to:
{
    parent: {
       sub: ['123']
    }
}

smashercosmo immutable.js بحالة متداخلة عميقة؟ أنا فضولي كيف

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

ليس لدينا كيانات داخل كيانات أخرى.

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

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

entities.plans[1] - three levels
entities.exercises[1] - three levels

هذا ليس كائنًا متداخلاً. مستوى واحد فقط.

{
   plans: [1,2, 3],
   exercises: [1,2,3],
   'so forth': [1,2,3]
}

wzup : لمعلوماتك ، لا يقضي دان الكثير من الوقت في البحث عن مشكلات Redux هذه الأيام - لقد حصل على ما يكفي من العمل على React.

معنى "التداخل" هنا هو عندما تكون البيانات نفسها متداخلة ، مثل هذا المثال السابق في السلسلة:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

في هذا المثال ، الطريقة الوحيدة للوصول إلى التمرين "exe6" هي البحث في الهيكل ، مثل plans[1].exercises[2] .

أنا مهتم بسؤالtmonte :

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

عندما يكون لديك العديد من الكيانات ، فقد يكون إنشاء مخفض واحد لكل كيان مؤلمًا ، ولكن هذا النهج يمكن أن يحل المشكلة. لم أجد حلاً لذلك حتى الآن.

أستخدم مسبقًا mergeWith بدلاً من merge لمزيد من المرونة:

import mergeWith from 'lodash/mergeWith';

// Updates an entity cache in response to any action with `entities`.
function entities(state = {}, action) {
  // Here where we STORE or UPDATE one or many entities
  // So check if the action contains the format we will manage
  // wich is `payload.entities`
  if (action.payload && action.payload.entities) {
    // if the entity is already in the store replace
    // it with the new one and do not merge. Why?
    // Assuming we have this product in the store:
    //
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    //
    // We will updated with
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //   }
    // }
    //
    // The result if we were using `lodash/merge`
    // notice the rate `rateCategory` hasn't changed:
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    // for this particular use case it's safer to use
    // `lodash/mergeWith` and skip the merge
    return mergeWith({}, state, action.payload.entities, (oldD, newD) => {
      if (oldD && oldD.id && oldD.id === newD.id) {
        return newD;
      }
      return undefined;
    });
  }

  // Here you could register other handlers to manipulate 
  // the entities
  switch (action.type) {
    case ActionTypes.SOME_ACTION:
      // do something;
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  entities,
});
export default rootReducer;
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات