使用 redux 工具包进行本地存储

Local storage using redux toolkit

我想将我的 isAuthenticated 状态保存在本地存储中,因此在刷新页面后,用户将登录。我尝试直接在 localStorage 中设置它 true/false 并设置初始值我在 redux 中的状态为这个值,但它总是将它设置为 true。

这是我的 redux 商店

import { createSlice, configureStore } from '@reduxjs/toolkit';

//MOVEMENTS (doesn't work yet)
const initialMovementsState = {
  movements: [],
};

const movementsSlice = createSlice({
  name: 'movements',
  initialState: initialMovementsState,
  reducers: {
    add(state) {
      //nothing yet
    },
    decrement(state) {
      //nothing yet
    },
  },
});

//LOGGING IN/OUT
const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

//STORE CONFIGURATION

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
});

export const movementsActions = movementsSlice.actions;
export const authActions = authSlice.actions;

export default store;

我找到的所有答案都只针对 redux,而不是 redux 工具包,而且我对 redux 还比较陌生,所以我迷路了。

您必须 dispatch loginlogout 操作才能真正改变您在 redux 存储中的状态!

更改 localStorage 是一种副作用,因此您不想在减速器中进行更改。减速器应该始终没有副作用。处理此问题的一种方法是使用自定义中间件。

编写中间件

我们的中间件在每个动作被调度后被调用。如果操作是 loginlogout,那么我们将更改 localStorage 值。否则我们什么都不做。无论哪种方式,我们都会使用 return next(action).

将操作传递给链中的下一个中间件

redux-toolkit 和 vanilla redux 中间件的唯一区别是我们如何检测 loginlogout 操作。使用 redux-toolkit,action creator 函数包括一个有用的 match() 函数,我们可以使用它而不必查看 type。我们知道,如果 login.match(action) 为真,则 action 是一个登录操作。所以我们的中间件可能看起来像这样:

const authMiddleware = (store) => (next) => (action) => {
  if (authActions.login.match(action)) {
    // Note: localStorage expects a string
    localStorage.setItem('isAuthenticated', 'true');
  } else if (authActions.logout.match(action)) {
    localStorage.setItem('isAuthenticated', 'false');
  }
  return next(action);
};

应用中间件

您将在 configureStore 函数中将中间件添加到您的商店。 Redux-toolkit includes some middleware by default 启用 thunk、不变性检查和可序列化检查。现在您根本没有在您的商店中设置 middleware 属性,因此您将获得所有默认设置。我们希望确保在添加自定义中间件时保留默认值。

middleware 属性 可以定义为一个函数,它被 redux-toolkit getDefaultMiddleware 函数调用。这允许您为默认中间件设置选项,如果您愿意,同时也可以添加我们自己的。我们将follow the docs举例并写成:

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  // Note: you can include options in the argument of the getDefaultMiddleware function call.
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authMiddleware)
});

不要这样做,因为它会删除所有默认中间件

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  middleware: [authMiddleware]
});

通过中间件同步状态

我们可以通过匹配 all auth 操作来简化我们的中间件。我们通过使用 String.prototype.startsWith() method on the action.type (similar to the examples in the addMatcher docs section 来做到这一点,它使用 .endswith()).

这里我们通过执行next(action) 找到下一个状态,然后我们改变localStorage。我们将 localStorage 值设置为 auth 切片返回的新状态。

const authMiddleware = (store) => (next) => (action) => {
  const result = next(action);
  if ( action.type?.startsWith('auth/') ) {
    const authState = store.getState().auth;
    localStorage.setItem('auth', JSON.stringify(authState))
  }
  return result;
};

或者您可以使用 redux-persist 包,它会为您完成。

与此同时,我已经编写了移动逻辑,并希望将我的所有状态保存在本地存储中。 Linda Paiste 的回答非常有帮助(如此长而直截了当的回答值得称赞!),但我正在努力将我的本地存储发送回我的 redux 状态。这是有效的解决方案:

import { createSlice, configureStore } from '@reduxjs/toolkit';
import dummyItems from '../helpers/dummyItems';

const initialMovementsState = {
  movements: dummyItems,
};

const movementsSlice = createSlice({
  name: 'movements',
  initialState: initialMovementsState,
  reducers: {
    add(state, action) {
      state.movements = [action.payload, ...state.movements];
    },
    delete(state, action) {
      const id = action.payload;
      state.movements = state.movements.filter(mov => mov.id !== id);
    },
  },
});

//AUTHORIZATION
const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

//MIDDLEWARE
const localStorageMiddleware = ({ getState }) => {
  return next => action => {
    const result = next(action);
    localStorage.setItem('applicationState', JSON.stringify(getState()));
    return result;
  };
};

const reHydrateStore = () => {
  if (localStorage.getItem('applicationState') !== null) {
    return JSON.parse(localStorage.getItem('applicationState')); // re-hydrate the store
  }
};

//STORE CONFIGURATION
const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  preloadedState: reHydrateStore(),
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(localStorageMiddleware),
});

export const movementsActions = movementsSlice.actions;
export const authActions = authSlice.actions;

export default store;

official documentation

和githubissue discussion