React低代码项目:Redux 状态管理

吐司问卷:Redux 状态管理

Date: February 18, 2025 5:37 PM (GMT+8)


Redux 管理用户信息

命名规范:

以 Info 结尾表示获取Reudx信息,比如 useGetUserInfo.ts

以 data 结尾表示获取服务端信息,比如 useLoadQuestionData



采用 Redux 管理用户信息

Redux store 设计:

src/store/index.ts

tsx 复制代码
import { configureStore } from '@reduxjs/toolkit'
import userReducer, { UserStateType } from './userReducer'

export type StateType = {
  user: UserStateType
}

export default configureStore({
  reducer: {
    user: userReducer,
  },
})

userReducer 开发:

src/store/userReducer.ts

tsx 复制代码
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

export type UserStateType = {
  username: string
  nickname: string
}

const INIT_STATE: UserStateType = {
  username: '',
  nickname: '',
}

export const userSlice = createSlice({
  name: 'user',
  initialState: INIT_STATE,
  reducers: {
    loginReducer: (
      state: UserStateType,
      action: PayloadAction<UserStateType>
    ) => {
      return action.payload
    },
    logoutReducer: () => {
      return INIT_STATE
    },
  },
})

export const { loginReducer, logoutReducer } = userSlice.actions
export default userSlice.reducer

src/index.ts

tsx 复制代码
 import React from 'react'
 import ReactDOM from 'react-dom/client'
+import { Provider } from 'react-redux'
+import store from './store'
 import App from './App'
 
 const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
 root.render(
   <React.StrictMode>
-    <App />
+    <Provider store={store}>
+      <App />
+    </Provider>
   </React.StrictMode>
 )


点击Logo跳转优化

**需求:**原本点击 Logo 会跳转到 home 页。现在需要根据是否有用户信息,进行区分判断。

如果有用户信息,点击 Logo 则跳转到管理页面。没有则到 home 页面。

src/component/Logo.tsx

tsx 复制代码
-import React, { FC } from 'react'
+import React, { FC, useState, useEffect } from 'react'
 import { Space, Typography } from 'antd'
 import { BlockOutlined } from '@ant-design/icons'
 import { Link } from 'react-router-dom'
+import useGetUserInfo from '../hooks/useGetUserInfo'
+import { HOME_PATHNAME, MANAGE_INDEX_PATHNAME } from '../router/index'
 import styles from './Logo.module.scss'
 
 const { Title } = Typography
 
 const Logo: FC = () => {
+  const { username } = useGetUserInfo()
+  const [pathname, setPathname] = useState<string>(HOME_PATHNAME)
+  useEffect(() => {
+    if (username) {
+      setPathname(MANAGE_INDEX_PATHNAME)
+    }
+  }, [username])
   return (
     <div className={styles.container}>
-      <Link to="/">
+      <Link to={pathname}>
         <Space>
           <Title>
             <BlockOutlined />


自定义 Hook 统一加载用户信息

思路:

  • 开发获取 Redux 中用户信息
  • 开发获取 服务端 用户信息,并与 Redux 联动
  • 设计页面加载状态(根据用户信息获取与否进行判断)
    • 有用户信息,则停止加载。没有则进行加载。

useGetUserInfo.tsx

tsx 复制代码
import { useSelector } from 'react-redux'
import { StateType } from '../store'
import { UserStateType } from '../store/userReducer'

function useGetUserInfo() {
  const { username, nickname } = useSelector<StateType>(
    (state: StateType) => state.user
  ) as UserStateType
  return { username, nickname }
}

export default useGetUserInfo

useLoadUserData.ts

tsx 复制代码
import { useEffect, useState } from 'react'
import { useRequest } from 'ahooks'
import { getUserInfoService } from '../services/user'
import { useDispatch } from 'react-redux'
import useGetUserInfo from './useGetUserInfo'
import { loginReducer } from '../store/userReducer'

function useLoadUserData() {
  const [waitingUserData, setWaitingUserData] = useState<boolean>(false)
  const dispatch = useDispatch()

  // 加载用户信息
  const { run } = useRequest(getUserInfoService, {
    manual: true,
    onSuccess: res => {
      const { username, nickname } = res
      // 存储到 Redux 中
      dispatch(loginReducer({ username, nickname }))
    },
    onFinally() {
      setWaitingUserData(false)
    },
  })

  // Redux 中数据
  const { username } = useGetUserInfo()
  useEffect(() => {
    if (username) {
      setWaitingUserData(false)
    }
    run()
  }, [username])

  return { waitingUserData }
}

export default useLoadUserData

用户退出功能优化

**思路:**用户退出不仅需要清空 Redux 信息,还要清空本地 token 信息

src/component/userInfo.tsx

tsx 复制代码
 import { LOGIN_PATHNAME } from '../router'
 import { Button, message } from 'antd'
 import { UserOutlined } from '@ant-design/icons'
-import { getUserInfoService } from '../services/user'
 import { removeToken } from '../utils/user-token'
-import { useRequest } from 'ahooks'
+import useGetUserInfo from '../hooks/useGetUserInfo'
+import { useDispatch } from 'react-redux'
+import { logoutReducer } from '../store/userReducer'
 
 const UserInfo: FC = () => {
   const nav = useNavigate()
-  const { data } = useRequest(getUserInfoService)
-  const { username, nickname } = data || {}
+  const { username, nickname } = useGetUserInfo()
+  const dispatch = useDispatch()
   const logout = () => {
-    removeToken()
+    dispatch(logoutReducer()) // 清空 redux user 中的数据
+    removeToken() // 清空 token 的存储
     message.success('退出成功')
     nav(LOGIN_PATHNAME)
   }


根据用户登录状态动态跳转页面

需求:

  • 当用户已经登陆,在问卷管理页面时,URL中输 /login,会自动返回问卷管理页面,而非再登陆

实现思路:

  • 设计 useNavPage 钩子函数:获取用户状态与URL,根据用户状态判断是否页面跳转走向。

useNavPage.ts

tsx 复制代码
import { useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import {
  isLoginOrRegister,
  isNoNeedUserInfo,
  MANAGE_INDEX_PATHNAME,
  LOGIN_PATHNAME,
} from '../router'
import useGetUserInfo from './useGetUserInfo'

function useNavPage(waitingUserData: boolean) {
  const { username } = useGetUserInfo()
  const { pathname } = useLocation()
  const nav = useNavigate()

  useEffect(() => {
    if (waitingUserData) return
    if (username) {
      if (isLoginOrRegister(pathname)) {
        nav(MANAGE_INDEX_PATHNAME)
      }
      return
    }
    if (isNoNeedUserInfo(pathname)) {
      return
    } else {
      nav(LOGIN_PATHNAME)
    }
  }, [waitingUserData, username, pathname])
}

export default useNavPage

src/router/index.ts

tsx 复制代码
export function isLoginOrRegister(pathname: string) {
  return [LOGIN_PATHNAME, REGISTER_PATHNAME].includes(pathname)
}

export function isNoNeedUserInfo(pathname: string) {
  return [HOME_PATHNAME, LOGIN_PATHNAME, REGISTER_PATHNAME].includes(pathname)
}



Bugfix

Q:请求不断发送

项目中有一处 请求会不断发送,在 Chrome Network 中可以看出。

useLoadUserData.ts

tsx 复制代码
useEffect(() => {
  if (username) {
    setWaitingUserData(false)
  }
  run() // 这里可能造成循环调用
}, [username])

问题原因:

  • 当Redux中的username为空时,run()会执行获取用户信息
  • 获取成功后通过dispatch(loginReducer)更新username
  • username更新触发useEffect再次执行
  • 形成「获取数据 → 更新username → 触发请求」的循环

解决方案:

修改useLoadUserData.ts中的useEffect:

tsx 复制代码
useEffect(() => {
  // 当已有用户信息时不再请求
  if (username) {
    setWaitingUserData(false)
    return
  }
  run()
}, [username]) // 保持原有依赖


Q:页面刷新自动弹回登陆界面

问题原因分析:

  1. useLoadUserData hook中,用户信息加载完成后才会设置waitingUserData为false
  2. useNavPage hook中会在用户信息未加载完成时判断路由权限
  3. 页面刷新时会清空Redux状态,导致需要重新加载用户信息
  4. 用户信息加载是异步操作,在加载完成前路由守卫已执行跳转判断

解决方案: 修改useLoadUserData.ts文件,在组件挂载时立即触发用户信息加载:

useLoadUserData.ts

tsx 复制代码
function useLoadUserData() {
  const [waitingUserData, setWaitingUserData] = useState<boolean>(true) // 初始值改为 true
  const dispatch = useDispatch()

  // 加载用户信息
  const { run } = useRequest(getUserInfoService, {
    manual: true,
    onSuccess: res => {
      const { username, nickname } = res
      dispatch(loginReducer({ username, nickname }))
    },
    onFinally() {
      setWaitingUserData(false)
    },
  })

  // 添加 useEffect 立即执行数据加载
  useEffect(() => {
    run()
  }, [])

  // Redux 中数据
  const { username } = useGetUserInfo()
  useEffect(() => {
    if (username) {
      setWaitingUserData(false)
    }
  }, [username])

  return { waitingUserData }
}

修改说明:

  1. waitingUserData初始值设为true,确保加载状态初始正确
  2. 添加立即执行的useEffect触发用户信息加载
  3. 保留原有的username依赖检查作为双重保障

同时需要确保在utils/user-token.ts中正确实现了token的持久化存储(根据代码片段推断应已实现)。

这个修改可以保证:

  1. 页面刷新时立即触发用户信息加载
  2. 在用户信息加载完成前保持等待状态
  3. 路由守卫(useNavPage)会等待用户信息加载完成后再进行跳转判断
  4. 避免在异步加载过程中误判未登录状态
相关推荐
范文杰2 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪2 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪2 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy3 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom3 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom3 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom3 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom3 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom4 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI4 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端