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

发布于:2025-02-27 ⋅ 阅读:(10) ⋅ 点赞:(0)

吐司问卷:Redux 状态管理

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


Redux 管理用户信息

命名规范:

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

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



采用 Redux 管理用户信息

Redux store 设计:

src/store/index.ts

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

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

 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

-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

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

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

 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

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


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

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

问题原因:

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

解决方案:

修改useLoadUserData.ts中的useEffect:

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



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

问题原因分析:

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

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

useLoadUserData.ts

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. 避免在异步加载过程中误判未登录状态