Expo,Redux Toolkit,为用户切片数据设置超时时间(session)

Expo, Redux Toolkit, set timeout for user slice data (session)

我正在为 Expo 应用程序使用 Redux Toolkit。在这个应用程序中,我有一个 user.js,它是一个 redux 工具包切片。此切片具有身份验证信息(用户数据、访问令牌、刷新令牌)。问题是服务器上的会话在 15 分钟后过期。

我的客户要求在 15 分钟后从应用程序注销用户。

我的问题是:如何在 redux 工具包切片中实现这一点?一种方法是在我的状态中添加一个 logged_at 时间戳,并且对于每个 API 请求,检查经过的时间(比较 logged_at > 现在),但我想让它成为正确的方式,我感觉这种方法不是很eloquent或可用。

如何在 X 时间过去后使用 redux/redux 工具包发送一个操作来注销用户?

简单方法:选择器

我觉得存储时间戳很合适。我可能更愿意存储过期时间戳 expiresAt 而不是 loggedAt.

您可以在选择器函数中添加一些逻辑,从而消除对显式“注销”操作的需要。我们可以将过期用户保留在状态中,但在访问之前对其进行验证。此函数仅 returns 用户对象(如果它仍然有效)和 null 如果它已经过期。

const selectUser = (state) => Date.now() > state.user.expiresAt ? null : state.user.user;

计划派送

您可以使用 setTimeout 安排 dispatch,但您必须在应用程序的最高级别而不是在您 dispatch 登录的组件中执行此操作。我创建了 a little demo,它会在 10 秒后自动注销。 useEffect 挂钩从状态(可以通过选择器派生)检测到 isLoggedIn 属性 的更改,并在 isLoggedIn 变为 true 时安排注销。

const App = () => {
  const isLoggedIn = useSelector((state) => state.user.isLoggedIn);

  const dispatch = useDispatch();

  useEffect(
    () => {
      if (isLoggedIn) {
        setTimeout(
          () => {
            dispatch(logOut());
            alert("Logged Out");
          },
          // log out after 10 seconds
          10 * 1000
        );
      }
    },
    // respond to changes in isLoggedIn
    [dispatch, isLoggedIn]
  );

  return <LogIn />;
};

Redux-Saga

您可以使用 redux-saga 安排注销以响应登录。如果用户在时间结束前自行注销,您可能希望创建一个非阻塞 fork with a delay for 15 minutes. With saga you can also cancel 任务。

它类似于 this example 重试 API 使用 2000 毫秒延迟的调用。


异步 Thunk

我们可以使用具有较长延迟的 createAsyncThunk 来安排 dispatch。用户可能已经注销,而其他用户可能会在此问题解决之前登录。您可以通过在 reducer 或 thunk 中检查用户名与当前用户名来处理此问题。

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

interface User {
  username: string;
}

type UserState = User | null;

const initialState = null as UserState;

const wait = (ms: number) =>
  new Promise<void>((resolve) => {
    setTimeout(() => resolve(), ms);
  });

export const asyncLogIn = createAsyncThunk(
  "user/asyncLogIn",
  async (user: User) => {
    await wait(10 * 1000);
    return user;
  }
);

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    // manual log out
    logOut() {
      return null;
    }
  },
  extraReducers: (builder) =>
    builder
      // log in when thunk is initiated
      .addCase(asyncLogIn.pending, (state, action) => {
        const user = action.meta.arg;
        return user;
      })
      // log out when thunk finally resolves
      .addCase(asyncLogIn.fulfilled, (state, action) => {
        // only log out if this user is still the logged in user
        if (action.payload.username === state?.username) {
          return null;
        }
      })
});

export const { logOut } = userSlice.actions;
export default userSlice.reducer;

Code Sandbox Demo