React Redux 私有路由最佳实践

React Redux Private Route Best Practise

专用路由的最佳做法是什么?也许我做错了什么,但是当用户登录时我已经重定向到 /login 页面

我的第二个问题:这些版本中哪个更好或者你有更好的主意?

代码:

授权

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: {},
    isUserLoggedIn: null,
    isLoading: false,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(me.pending, (state) => {
        state.isLoading = true
      })
      .addCase(me.fulfilled, (state, action) => {
        state.user = action.payload.userData
        state.isUserLoggedIn = true
        state.isLoading = false
      })
      .addCase(me.rejected, (state) => {
        state.isUserLoggedIn = false
      })
  },
})

export const me = createAsyncThunk('auth/me', async () => {
  try {
    const user = await userService.getUserData()
    return { userData: user.data.data }
  } catch (error) {
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString()
    return message
  }
})

案例 1:

应用

function App() {
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(me())
  }, [])

  const auth = useSelector((state) => state.auth)

  return (
    <div data-theme={theme}>
      <BrowserRouter>
        <AppRoutes isAuthenticated={auth.isUserLoggedIn} />
      </BrowserRouter>
    </div>
  )
}

export default App

路线

export const AppRoutes = ({ isAuthenticated }) => (
  <Routes>
    <Route
      path='/login'
      element={<Login />}
    />
    <Route
      path='/dashboard'
      element={
        <PrivateRoute isAuthenticated={isAuthenticated}>
          <Stats />
        </PrivateRoute>
      }
    />
    ...

私有路由

export const PrivateRoute = ({ children, isAuthenticated }) => {
  return isAuthenticated ? children : <Navigate to='/login' />
}

案例 2:

应用

function App() {
  return (
    <div data-theme={theme}>
      <BrowserRouter>
        <AppRoutes />
      </BrowserRouter>
    </div>
  )
}

export default App

路线

export const AppRoutes =  () => (
  <Routes>
    <Route
      path='/login'
      element={<Login />}
    />
    <Route
      path='/dashboard'
      element={
        <PrivateRoute>
          <Stats />
        </PrivateRoute>
      }
    />
  ...

私有路由

export const PrivateRoute = ({ children }) => {
  const dispatch = useDispatch()
  const { isUserLoggedIn } = useSelector((state) => state.auth)

  useEffect(() => {
    if (isUserLoggedIn === null) {
      dispatch(me())
    }
  }, [])

  return isUserLoggedIn ? children : <Navigate to='/login' />
}

案例 1 或案例 2 更适合处理,或者您有更好的主意?

对于这 2 个想法,当我转到 /dashboard 时,它会很快重定向到 /login

我想要实现的是良好的实践、快速的验证,并等待我们从后端收到用户已通过身份验证的肯定响应

团队,你有什么建议?

编辑

新版本:

Redux

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: {},
    isUserLoggedIn: null,
    isLoading: true,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(me.pending, (state) => {
        state.isLoading = true
      })
      .addCase(me.fulfilled, (state, action) => {
        state.user = action.payload.userData
        state.isUserLoggedIn = true
        state.isLoading = false
      })
      .addCase(me.rejected, (state) => {
        state.isLoading = false
        state.isUserLoggedIn = false
      })
  },
})

export const me = createAsyncThunk('auth/me', async ({}, thunkAPI) => {
  try {
    const user = await userService.getUserData()
    return { userData: user.data.data }
  } catch (error) {
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString()
    thunkAPI.rejectWithValue(message)
    return message
  }
})

私有路由

export const PrivateRoute = ({ children }) => {
  const dispatch = useDispatch()
  const { isLoading, isUserLoggedIn } = useSelector((state) => state.auth)

  if (isLoading) return null 

  return isUserLoggedIn ? (
    children
  ) : (
    <Navigate
      to='/login'
      replace
    />
  )
}

应用程序

function App() {
  const dispatch = useDispatch()
  const { isUserLoggedIn } = useSelector((state) => state.auth)

  useEffect(() => {
    if (isUserLoggedIn === null) {
      dispatch(me())
    }
  }, [])

  return (
    <div data-theme={theme}>
      <BrowserRouter>
        <AppRoutes />
      </BrowserRouter>
    </div>
  )
}

export default App

商店

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import authReducer from '../features/auth/authSlice'

export const store = configureStore({
  reducer: {
    auth: authReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
})

Index.js

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')

但是当我重新加载页面时,我会转到 /login 不要像 /dashboard 那样停留在同一页面,我该如何应对?

这两种方法都可以。您看到的问题是基于用于身份验证检查和重定向 prior 的初始 redux 状态值 运行 set授权状态。在确定身份验证状态之前,您将希望推迟重定向。使用状态呈现 null 或一些加载指示器,直到用户通过身份验证。

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: {},
    isUserLoggedIn: null,
    isLoading: true, // <-- assume initially loading state from mouting
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(me.pending, (state) => {
        state.isLoading = true
      })
      .addCase(me.fulfilled, (state, action) => {
        state.user = action.payload.userData
        state.isUserLoggedIn = true
        state.isLoading = false
      })
      .addCase(me.rejected, (state) => {
        state.isUserLoggedIn = false
      })
  },
});

在这两种实现之间,IMO 首选第二种,因为它可以减少组件耦合。我建议将两者结合起来。在 App 中检查用户并发送操作以设置身份验证状态,并在私有路由中检查身份验证状态。

function App() {
  const dispatch = useDispatch();
  const { isUserLoggedIn } = useSelector((state) => state.auth);

  useEffect(() => {
    if (isUserLoggedIn === null) {
      dispatch(me());
    }
  }, []);

  return (
    <div data-theme={theme}>
      <BrowserRouter>
        <AppRoutes />
      </BrowserRouter>
    </div>
  );
}

...

export const PrivateRoute = ({ children }) => {
  const dispatch = useDispatch()
  const { isLoading, isUserLoggedIn } = useSelector((state) => state.auth)

  if (isLoading) return null; // <-- or loading spinner, etc...

  return isUserLoggedIn ? children : <Navigate to='/login' replace />
}

第一个版本也可以实现同样的效果,你只需要将 isLoading 状态连同 isUserLoggedIn 状态作为 props 传递给 PrivateRoute

我发现了一个问题!呵呵!

看似愚蠢 && 简单但它开始工作了 ->

Redux

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: {},
    isUserLoggedIn: null,
    isLoading: true,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(me.pending, (state) => {
        state.isLoading = true
      })
      .addCase(me.fulfilled, (state, action) => {
        state.user = action.payload.userData
        state.isUserLoggedIn = true
        state.isLoading = false
      })
      .addCase(me.rejected, (state) => {
        state.isLoading = false
        state.isUserLoggedIn = false
      })
  },
})

export const me = createAsyncThunk('auth/me', async (_, thunkAPI) => { // <- {} replaced to _ and this is it! 
  try {
    const user = await userService.getUserData()
    return { userData: user.data.data }
  } catch (error) {
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString()
    thunkAPI.rejectWithValue(message)
    return message
  }
})