javascript - Hur återställer man tillståndet för en Redux-store?


Jag använder Redux för tillståndshantering.
Hur återställer jag butiken till dess initiala tillstånd?

Till exempel, låt oss säga att jag har två användarkonton (u1 och u2).
Föreställ dig följande sekvens av händelser:

  1. Användaren u1 loggar in i appen och gör något, så vi cachar viss data i lagret.

  2. Användare u1 loggar ut.

  3. Användaren u2 loggar in i appen utan att uppdatera webbläsaren.

Vid det här laget kommer de cachade uppgifterna att vara kopplade till u1, och jag skulle vilja rensa dem.

Hur kan jag återställa Redux-butiken till dess ursprungliga tillstånd när den första användaren loggar ut?

Svar #1

Ett sätt att göra det skulle vara att skriva en root reducer i din applikation.

Rotreduceraren skulle normalt delegera hanteringen av åtgärden till reduceraren som genererats av combineReducers(). Men när den får USER_LOGOUT-åtgärden, returnerar den initialt tillstånd på nytt.

Till exempel, om din root-reducer såg ut så här:

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

Du kan byta namn på det till appReducer och skriva en ny rootReducer som delegerar till det:

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

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

Nu behöver vi bara lära den nya rootReducer att returnera det initiala tillståndet som svar på USER_LOGOUT-åtgärden. Som vi vet ska reducers returnera det initiala tillståndet när de kallas med undefined som första argument, oavsett åtgärd. Låt oss använda detta faktum för att villkorligt ta bort det ackumulerade state när vi för vidare det till appReducer:

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

  return appReducer(state, action)
}

Nu, närhelst USER_LOGOUT utlöses, kommer alla reduktionsfunktioner att initialiseras på nytt. De kan också returnera något annat än de gjorde ursprungligen om de vill eftersom de också kan kontrollera action.type.

För att upprepa, ser den fullständiga nya koden ut så här:

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)
}

Om du använder redux-persist kan du också behöva rensa ditt lagringsutrymme. Redux-persist behåller en kopia av ditt tillstånd i en lagringsmotor, och tillståndskopian kommer att laddas därifrån vid uppdatering.

Först behöver du importera den lämpliga lagringsmotorn och sedan analysera tillståndet innan du ställer in det på undefined och rensar varje lagringsnyckel.

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);
};

Svar #2

Dan Abramovs svar är korrekt förutom att vi upplevde ett märkligt problem när vi använde paketet react-router-redux tillsammans med detta tillvägagångssätt.

Vår lösning var att inte sätta tillståndet till undefined utan istället fortfarande använda den nuvarande routing-reduceraren. Så jag skulle föreslå att implementera lösningen nedan om du använder detta paket

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

Svar #3

Definiera en handling:

const RESET_ACTION = {
  type: "RESET"
}

Sedan i varje av dina reducers, förutsatt att du använder switch eller if-else för att hantera flera actions genom varje reducer. Jag kommer att ta fallet för en 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; 
  }
}

På detta sätt, när du anropar RESET-åtgärden, kommer din reducer att uppdatera lagret med standardtillståndet.

Nu kan du hantera utloggning på följande sätt:

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

Varje gång en användare loggar in, utan att uppdatera webbläsaren. Lagret kommer alltid att vara på standardvärdet.

store.dispatch(RESET_ACTION) förklarar bara idén. Du kommer troligtvis att ha en action creator för detta ändamål. Ett mycket bättre sätt är att du har en LOGOUT_ACTION.

När du skickar detta LOGOUT_ACTION. Kan en anpassad middleware sedan avlyssna denna åtgärd, antingen med Redux-Saga eller Redux-Thunk. På båda sätt kan du dock skicka en annan åtgärd 'RESET'. På så sätt kommer utloggning och återställning av lagret ske synkront och ditt lager kommer vara redo för en annan användares inloggning.

Svar #4

Bara ett förenklat svar på Dan Abramovs svar:

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


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

Svar #5

Använder Redux Toolkit och/eller 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);
};

Svar #6

Ur ett säkerhetsperspektiv är det säkraste att göra när man loggar ut en användare att återställa allt bestående tillstånd (t.ex. cookies, localStorage, IndexedDB, Web SQL, etc.) och göra en hård uppdatering av sidan med window.location.reload(). Det är möjligt att en slarvig utvecklare av misstag eller med flit har lagrat känslig data på window, i DOM:en, etc. Att ta bort allt bestående tillstånd och uppdatera webbläsaren är det enda sättet att garantera att ingen information från den föregående användaren läcker till nästa användare.

(Självklart, som användare på en delad dator bör du använda "privat surfning"-läge, stänga webbläsarfönstret själv, använda funktionen "rensa webbläsardata" osv., men som utvecklare kan vi inte förvänta oss att alla alltid är så noggranna)

Svar #7

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

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

   return state
 }

Du kan också utlösa en åtgärd som hanteras av alla eller vissa reducers, som du vill återställa till den ursprungliga lagringen. En åtgärd kan utlösa en återställning av hela ditt tillstånd, eller bara en del av det som verkar lämpligt för dig. Jag tror att detta är det enklaste och mest kontrollerbara sättet att göra detta.

Svar #8

Med Redux har jag tillämpat följande lösning, som antar att jag har satt ett initialState i alla mina reducers (t.ex. { user: { name, email }}). I många komponenter kontrollerar jag dessa nästlade egenskaper, så med denna fix förhindrar jag att mina renderingsmetoder går sönder vid beroende av egenskapsvillkor (t.ex. om state.user.email, vilket skulle ge ett fel user är odefinierad om de ovan nämnda lösningarna används).

const appReducer = combineReducers({
  tabs,
  user
})

const initialState = appReducer({}, {})

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

  return appReducer(state, action)
}

Relaterade artiklar