React Redux私有路由最佳实践

3

私有路由的最佳实践是什么?也许我做错了什么,但是当用户登录时,我已经重定向到 /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' />
}

CASE 1或CASE 2哪个更好,或者你有更好的想法吗?
对于这两个想法,在我进入/仪表板时,它会非常快速地重定向到/登录。
我的目标是良好的实践、快速验证,并等待我们从后端收到用户已经通过身份验证的积极响应。
团队,你们有什么建议?
编辑:
新版本:
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
  }
})

PrivateRoute

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这个页面,我该怎么解决这个问题?
2个回答

2

两种方法都可以。你看到的问题是基于初始redux状态值用于身份验证检查和重定向之前生效,以设置身份验证状态。您需要等待身份验证状态确定后再进行重定向。使用状态来渲染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
      })
  },
});

在这两种实现方式之间,我认为第二种更好,因为它减少了组件之间的耦合度。我建议将两种方式结合起来使用。检查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状态作为道具传递给PrivateRoute


太好了!我按照你说的做了,但是还剩下一个问题->当我重新加载页面时,它会跳转到/login(因为需要一些时间来验证)。有什么想法吗? - Time Buy
重新加载页面后,我会进入/login页面,也许我们应该使用一些历史记录并返回到先前的路由?那性能方面呢?这是一个好主意吗?最佳方法是什么? :) - Time Buy

0

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

看起来很愚蠢和简单,但它开始工作了 ->

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接