react中useEffect的用法,以及订阅模式的原理

退出登录的功能示例

javascript 复制代码
import React, { FC, useEffect } from 'react'
import { Button, message } from 'antd'
import { Link, useNavigate } from 'react-router-dom'
import { UserOutlined } from '@ant-design/icons'
import { useDispatch } from 'react-redux'
// import { useRequest } from 'ahooks'
import { LOGIN_PATHNAME } from '../router'
// import { getUserInfoService } from '../services/user'
import { removeToken } from '../utils/user-token'
import useGetUserInfo from '../hooks/useGetUserInfo'
import { logoutReducer } from '../store/userReducer'

const UserInfo: FC = () => {
  const nav = useNavigate()
  const dispatch = useDispatch()

  // const { data } = useRequest(getUserInfoService) // ajax
  // const { username, nickname } = data || {}
  const { username, nickname } = useGetUserInfo() // 从 redux 中获取用户信息

  function logout() {
    dispatch(logoutReducer()) // 清空了 redux user 数据
    removeToken() // 清除 token 的存储
    message.success('退出成功')
  }

  // 监听用户状态变化,如果用户已退出则跳转到登录页
  useEffect(() => {
    if (!username) {
      nav(LOGIN_PATHNAME)
    }
  }, [username, nav])

  const UserInfo = (
    <>
      <span style={{ color: '#e8e8e8' }}>
        <UserOutlined />
        {nickname}
      </span>
      <Button type="link" onClick={logout}>
        退出
      </Button>
    </>
  )

  const Login = <Link to={LOGIN_PATHNAME}>登录</Link>

  return <div>{username ? UserInfo : Login}</div>
}

export default UserInfo

用法说明

这个 useEffect 是用来监听用户登出状态并执行相应跳转的核心逻辑。让我详细解释一下它是如何工作的:

javascript 复制代码
useEffect(() => {
  if (!username) {
    nav(LOGIN_PATHNAME)
  }
}, [username, nav])

这段代码的作用是:

  1. 监听依赖项 :第二个参数 [username, nav] 表示这个 effect 依赖于 usernamenav 这两个变量
  2. 触发条件 :只有当 usernamenav 的值发生变化时,才会重新执行 effect 中的函数
  3. 执行逻辑
    • 检查 username 是否为空(falsy值)
    • 如果为空,就调用 nav(LOGIN_PATHNAME) 跳转到登录页面

具体流程

当你点击"退出"按钮时:

  1. 触发 logout 函数
  2. logout 函数中执行 dispatch(logoutReducer())
  3. logoutReducer 会把 Redux store 中的用户信息重置为初始状态 { username: '', nickname: '' }
  4. 由于 useGetUserInfo() hook 会订阅 Redux store 中的用户信息,所以 username 变量会从实际用户名变为 ' '
  5. useEffect 检测到 username 发生了变化,于是重新执行其中的函数
  6. 此时 !username 为 true(因为 username 是空字符串),所以执行 nav(LOGIN_PATHNAME)
  7. 页面跳转到登录页 /login

订阅模式介绍

在 React 和 Redux 中,"订阅"是一种设计模式,用于监听状态变化并在状态更新时自动通知相关组件。让我详细解释一下这个过程:
Redux 订阅机制的工作原理
Redux 使用发布-订阅(Publish-Subscribe)模式来实现状态变化的通知机制。下面是具体的工作流程:
`

  1. 订阅建立:当组件使用 useGetUserInfo() 这样的自定义 hook 时,它内部会使用 react-redux 提供的 useSelector hook 来订阅 Redux store 中的特定状态。
  2. 状态变化检测:每当 dispatch 一个 action(例如 logoutReducer())时,Redux 会更新 store 中的状态。
  3. 通知机制:Redux 会比较更新前后的状态,如果发现有变化,就会通知所有订阅了这部分状态的组件。
  4. 组件重新渲染:接收到通知的组件会使用新的状态值重新渲染。`

具体到我们的例子

javascript 复制代码
// useGetUserInfo hook 内部大概是这样的
export default function useGetUserInfo() {
  const { username, nickname } = useSelector<StateType>(state => state.user)
  return { username, nickname }
}

// 在 UserInfo 组件中
const { username, nickname } = useGetUserInfo()

当执行 dispatch(logoutReducer()) 时:

  1. Redux store 中的 user 状态从 { username: 'actual_username', nickname: 'actual_nickname' } 变成了 { username: '', nickname: '' }
  2. Redux 检测到这个变化
  3. 所有使用 useSelector 订阅了 state.user 的组件都会收到通知
  4. UserInfo 组件重新渲染,此时 username 变量的值变成了 ' '

useEffect 如何响应变化

javascript 复制代码
useEffect(() => {
  if (!username) {
    nav(LOGIN_PATHNAME)
  }
}, [username, nav])

这里的 [username, nav] 是依赖数组,告诉 React:"当 username 或 nav 变化时,请重新执行这个 effect"。

所以整个流程是:

  1. 用户点击退出按钮

  2. dispatch logoutReducer() 清空用户信息

  3. Redux 通知订阅了用户信息的所有组件

  4. UserInfo 组件重新渲染,username 更新为空字符串

  5. useEffect 检测到 username 变化,执行回调函数

  6. 因为 !username 为 true,执行跳转到登录页

这就是所谓的"订阅模式"------组件订阅了状态的一部分,当这部分状态变化时,组件会自动得到通知并做出响应。这是一种非常高效且常用的 React 状态管理模式。

Effect的概念

Effect 是 React 中的一个核心概念,指的是在组件渲染之外执行的操作。这些操作通常包括:

  1. 数据获取(API 请求)
  2. 订阅(如 WebSocket 连接)
  3. 手动修改 DOM
  4. 定时器(setTimeout, setInterval)
  5. 日志记录
  6. 页面跳转(路由导航)

在传统的 Class 组件中,这些操作通常在 componentDidMount、componentDidUpdate 和 componentWillUnmount 等生命周期方法中处理。

useEffect Hook

React Hooks 引入了 useEffect 来统一处理这些副作用。它的基本语法是:

javascript 复制代码
useEffect(() => {
  // 副作用代码
  // 可选的清理函数
  return () => {
    // 清理代码
  };
}, [/* 依赖数组 */]);

这里的 effect 做的事情是:

  1. 副作用操作 :页面跳转 (nav(LOGIN_PATHNAME))
  2. 触发条件:当 username 变为空时
  3. 依赖项:username 和 nav

为什么叫"Effect"

之所以叫做"effect",是因为它处理的是"副作用",即不是直接参与渲染过程,但却会产生重要影响的操作。在函数式编程中,纯函数不应该有任何副作用,而 effect 正是用来处理这些不可避免的副作用的机制。

Effect 的工作机制

  1. 初始化:组件首次渲染后,执行 effect 函数(如果提供了依赖数组且为空,则只执行一次)
  2. 依赖更新:当依赖数组中的任何一个值发生变化时,重新执行 effect 函数
  3. 清理:如果 effect 函数返回了一个清理函数,它会在下一次 effect 执行前或者组件卸载时被调用

在我们的例子中:

  • 当用户点击退出按钮,触发 logout
  • logout 清除了 Redux 中的用户信息
  • username 从真实用户名变成空字符串
  • useEffect 检测到 username 变化,执行 effect 函数
  • effect 函数发现 !username 为真,执行页面跳转

这就是 effect 的完整工作流程:监听特定状态变化,当变化发生时执行相应的副作用操作。

相关推荐
LYFlied2 小时前
【每日算法】LeetCode 17. 电话号码的字母组合
前端·算法·leetcode·面试·职场和发展
前端白袍2 小时前
Vue:如何实现一个具有复制功能的文字按钮?
前端·javascript·vue.js
new code Boy3 小时前
escape谨慎使用
前端·javascript·vue.js
叠叠乐3 小时前
robot_state_publisher 参数
java·前端·算法
Kiri霧3 小时前
Range循环和切片
前端·后端·学习·golang
小张快跑。3 小时前
【Java企业级开发】(十一)企业级Web应用程序Servlet框架的使用(上)
java·前端·servlet
小白阿龙3 小时前
Flex布局子元素无法垂直居中
前端
秋田君4 小时前
前端工程化部署入门:Windows + Nginx 实现多项目独立托管与跨域解决方案
前端·windows·nginx
江城开朗的豌豆4 小时前
阿里邮件下载器使用说明
前端