جافاسكريبت - كيف إعادة تعيين حالة متجر Redux؟


أنا أستخدم Redux لإدارة الحالة.
كيف يمكنني إعادة المتجر إلى حالته الأولية؟

على سبيل المثال، لنفترض أن لدي حسابي مستخدمين (u1 و u2).
تخيل التسلسل التالي للأحداث:

  1. يقوم المستخدم u1 بتسجيل الدخول إلى التطبيق ويفعل شيئًا ما، لذلك نقوم بتخزين بعض البيانات في المخزن المؤقت.

  2. المستخدم u1 يقوم بتسجيل الخروج.

  3. يقوم المستخدم u2 بتسجيل الدخول إلى التطبيق دون تحديث المتصفح.

في هذه المرحلة، سيتم ربط البيانات المخزنة مؤقتًا بـ u1، وأود تنظيفها.

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

الإجابة رقم 1

إحدى الطرق للقيام بذلك هي كتابة مُخفض جذر في تطبيقك.

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

على سبيل المثال، إذا كان المجمع الجذري لديك يبدو هكذا:

const rootReducer = combineReducers({
  /* your app’s top-level reducers */
})

يمكنك إعادة تسميته إلى appReducer وكتابة rootReducer جديد يفوض إليه:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  return appReducer(state, action)
}

الآن نحتاج فقط إلى تعليم rootReducer الجديد لإرجاع الحالة الأولية استجابةً للإجراء USER_LOGOUT. كما نعلم، من المفترض أن تُرجع الريديورز الحالة الأولية عندما يتم استدعاؤها بـ undefined كالحجة الأولى، بغض النظر عن الإجراء. دعونا نستخدم هذه الحقيقة لنقوم بشطب state المتراكم بشكل مشروط أثناء تمريره إلى appReducer:

 const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    return appReducer(undefined, action)
  }

  return appReducer(state, action)
}

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

للتأكيد، الكود الجديد الكامل يبدو هكذا:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    return appReducer(undefined, action)
  }

  return appReducer(state, action)
}

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

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

const rootReducer = (state, action) => {
    if (action.type === SIGNOUT_REQUEST) {
        // for all keys defined in your persistConfig(s)
        storage.removeItem('persist:root')
        // storage.removeItem('persist:otherKey')

        return appReducer(undefined, action);
    }
    return appReducer(state, action);
};

الإجابة رقم 2

إجابة دان أبراموف صحيحة باستثناء أننا واجهنا مشكلة غريبة عند استخدام حزمة react-router-redux مع هذا النهج.

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

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    const { routing } = state
    state = { routing } 
  }
  return appReducer(state, action)
}

الإجابة رقم 3

حدد إجراءً:

const RESET_ACTION = {
  type: "RESET"
}

ثم في كل واحد من الـ reducers الخاصة بك بافتراض أنك تستخدم switch أو if-else للتعامل مع عدة إجراءات من خلال كل reducer. سأأخذ الحالة الخاصة بـ switch.

const INITIAL_STATE = {
  loggedIn: true
}

const randomReducer = (state=INITIAL_STATE, action) {
  switch(action.type) {
    case 'SOME_ACTION_TYPE':

       //do something with it

    case "RESET":

      return INITIAL_STATE; //Always return the initial state

   default: 
      return state; 
  }
}

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

الآن، لتسجيل الخروج يمكنك التعامل معه كما يلي:

const logoutHandler = () => {
    store.dispatch(RESET_ACTION)
    // Also the custom logic like for the rest of the logout handler
}

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

store.dispatch(RESET_ACTION) يوضّح الفكرة فقط. من المحتمل جداً أن يكون لديك منشئ إجراءات لهذا الغرض. الطريقة الأفضل هي أن يكون لديك LOGOUT_ACTION.

بمجرد إرسال هذا LOGOUT_ACTION. يمكن لبرنامج وسيط مخصص أن يعترض هذا الإجراء، إما باستخدام Redux-Saga أو Redux-Thunk. ومع ذلك، في كلتا الطريقتين، يمكنك إرسال إجراء آخر 'RESET'. بهذه الطريقة ستحدث عملية تسجيل الخروج وإعادة التعيين لمخزن البيانات بشكل متزامن وسيكون المخزن جاهزًا لتسجيل دخول مستخدم آخر.

الإجابة رقم 4

مجرد إجابة مبسطة على إجابة دان أبراموف:

const rootReducer = combineReducers({
    auth: authReducer,
    ...formReducers,
    routing
});


export default (state, action) =>
  rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);

الإجابة رقم 5

باستخدام Redux Toolkit و/أو Typescript:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
});

const rootReducer = (
  state: ReturnType<typeof appReducer>,
  action: AnyAction
) => {
/* if you are using RTK, you can import your action and use it's type property instead of the literal definition of the action  */
  if (action.type === logout.type) {
    return appReducer(undefined, { type: undefined });
  }

  return appReducer(state, action);
};

الإجابة رقم 6

من منظور أمني، فإن أكثر شيء أمانًا للقيام به عند تسجيل خروج المستخدم هو إعادة ضبط جميع الحالات المستمرة (مثل الكوكيز، localStorage، IndexedDB، Web SQL، إلخ) وإجراء تحديث كامل للصفحة باستخدام window.location.reload(). من الممكن أن يكون أحد المطورين المهملين قد قام عن طريق الخطأ أو عن قصد بتخزين بعض البيانات الحساسة على window، في DOM، إلخ. إن مسح جميع الحالات المستمرة وتحديث المتصفح هو الطريقة الوحيدة لضمان عدم تسرب أي معلومات من المستخدم السابق إلى المستخدم التالي.

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

الإجابة رقم 7

 const reducer = (state = initialState, { type, payload }) => {

   switch (type) {
      case RESET_STORE: {
        state = initialState
      }
        break
   }

   return state
 }

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

الإجابة رقم 8

مع Redux إذا قمت بتطبيق الحل التالي، الذي يفترض أنني قد ضبطت initialState في جميع المخفضات الخاصة بي (على سبيل المثال { user: { name, email }}). في العديد من المكونات، أقوم بالتحقق من هذه الخصائص المتداخلة، لذا مع هذا التصحيح، أمنع طرق العرض الخاصة بي من التعطل عند شروط الخصائص المترابطة (على سبيل المثال إذا state.user.email، وهو ما سيتسبب في حدوث خطأ user is undefined إذا تم استخدام الحلول المذكورة أعلاه).

const appReducer = combineReducers({
  tabs,
  user
})

const initialState = appReducer({}, {})

const rootReducer = (state, action) => {
  if (action.type === 'LOG_OUT') {
    state = initialState
  }

  return appReducer(state, action)
}