从全局登录状态管理学习 Redux

1. 背景与痛点:看起来很美的 Context API

业务场景:

做一个后台管理系统。用户登录后,我们需要在多个地方用到用户状态:

  • 顶部导航栏 (NavBar): 显示用户头像和昵称。
  • 路由守卫 (Router): 根据用户的 role 权限,判断他能不能进"系统设置"页面。
  • 个人中心 (Profile): 允许用户修改自己的昵称。

最初的尝试:

因为 React 自带了 Context API,本着"能少装一个包就少装一个"的心态,我用 Context + useReducer 把 UserInfo 和 Token 存在了全局根组件。

踩坑:

跑起来确实能用,但我打开 React DevTools 的 Profiler 录制性能时,冷汗冒出来了。

当我在"个人中心"修改了一下用户昵称,整个应用(包括侧边栏、甚至毫无关系的数据图表组件)竟然全部触发了重新渲染(Re-render)!如果图表里有复杂的计算,页面直接卡顿了一下。

2. 为什么 Context 会引发"广播风暴"?

很多新手在这个地方都会踩坑,以为 Context 是万能的全局状态管理工具。底层逻辑其实很简单:

  • Context 的无差别广播: Context 的设计初衷是为了"依赖注入"(比如切换暗黑主题、多语言),而不是为了"高频状态管理"。只要 Provider 里的 value 变了,所有消费了这个 Context 的组件,不管你具体用到了里面的哪个字段,都会被强行重新渲染。
  • Redux 的精准订阅: 这就是为什么我们需要 Redux。通过 useSelector(state => state.user.name),Redux 在底层做了一层细粒度的依赖订阅。只有 name 变了,NavBar 才会更新,侧边栏和图表组件根本不会收到影响。

3. 跨越框架的执念:Redux 为什么这么"死板"?

既然决定用 Redux,但刚上手时我非常难受。习惯了 Vue 的同学肯定懂:在 Vue 里想改个名字,直接 state.user.name = '新名字' 就行了。

但在传统的 Redux 里,你必须写:

javascript 复制代码
// 传统 Redux 极其折磨的写法(浅拷贝地狱)
function userReducer(state = initialState, action) {
  switch (action.type) {
    case 'UPDATE_NAME':
      return {
        ...state,
        user: {
          ...state.user,
          name: action.payload
        }
      };
    // ...更多样板代码
  }
}

为什么要这样?

其实这和 React 的渲染机制有关。React 采用的是**"浅比较(Shallow Compare)"**策略,也就是直接比较对象的内存地址(拍快照)。如果你直接修改原对象,内存地址没变,React 就会认为"数据没变",从而拒绝更新视图。所以,我们必须返回一个全新的对象(新的快照地址)。

对比之下,Vue3 底层使用了 Proxy 劫持对象属性,能够自动收集依赖并触发更新,所以不需要你手动去维护这种"不可变数据(Immutability)"。

4. 方案:现代 Redux Toolkit (RTK)

难道写 React 就注定要忍受这种样板代码吗?当然不是。现代 Redux 官方强烈推荐使用 Redux Toolkit (RTK) 。它内置了 Immer.js,让我们可以像写 Vue 一样写 Redux!

来看看用 RTK + TypeScript 改造后的全局登录状态管理有多优雅:

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

// 1. 规范的类型定义(严谨的工程思维)
interface UserInfo {
  id: string;
  name: string;
  role: 'admin' | 'editor' | 'viewer';
  avatar: string;
}

interface AuthState {
  token: string | null;
  userInfo: UserInfo | null;
  isAuthenticated: boolean;
}

const initialState: AuthState = {
  token: localStorage.getItem('token') || null,
  userInfo: null,
  isAuthenticated: false,
};

// 2. 核心:使用 createSlice
export const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    // 登录成功,保存数据
    loginSuccess: (state, action: PayloadAction<{ token: string; user: UserInfo }>) => {
      // 魔法时刻:得益于 Immer.js,我们可以直接"修改" state!
      // 底层会自动帮我们转化成不可变数据的"新快照"
      state.token = action.payload.token;
      state.userInfo = action.payload.user;
      state.isAuthenticated = true;
    },
    // 更新昵称
    updateName: (state, action: PayloadAction<string>) => {
      if (state.userInfo) {
        state.userInfo.name = action.payload; // 直接赋值,告别浅拷贝地狱!
      }
    },
    // 登出清理
    logout: (state) => {
      state.token = null;
      state.userInfo = null;
      state.isAuthenticated = false;
      localStorage.removeItem('token');
    },
  },
});

export const { loginSuccess, updateName, logout } = authSlice.actions;
export default authSlice.reducer;

5. 总结

作为前端新人,从自己写 Demo 到接触企业级工程,最大的转变就是不要仅仅为了"能跑通"而写代码

通过这次登录状态的重构,我不仅解决了 Context 的性能瓶颈,理清了 React 的不可变数据哲学,更学会了使用 RTK 这种现代化的工程利器。工具在变,但底层的渲染原理和架构思维才是我们最应该掌握的核心。

相关推荐
ting94520001 小时前
告别无效学习:Scholé 如何用 AI 重构职场学习,让学习直接嵌入工作流
人工智能·学习·重构
xian_wwq1 小时前
【学习笔记】Harness到底是什么
笔记·学习·ai·harness
爱看书的小沐1 小时前
【小沐杂货铺】基于Three.js绘制三维艺术画廊3DArtGallery (Three.js,WebGL)
javascript·3d·webgl·three.js·babylon.js·三维画廊
wuxinyan1231 小时前
大模型学习之路004:RAG 零基础入门教程(第一篇):基础理论与文档处理流水线
人工智能·学习·rag
冯诺依曼的锦鲤2 小时前
从零实现高并发内存池:TCMalloc 核心架构拆解
c++·学习·算法·架构
陈随易2 小时前
2年没用Nodejs了,Bun很香
前端·后端·程序员
donecoding2 小时前
Corepack 完全解析:从懵到懂,包管理器自由了
前端·node.js·前端工程化
yqcoder2 小时前
端经典面试题:为什么 0.1 + 0.2 !== 0.3?
前端·css
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_12:(HTML网页图片嵌入)
前端·javascript·css·ui·html