news 2026/9/18 22:45:17

Redux 实战:真实项目案例盘点与基于 Redux 的认证登录完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redux 实战:真实项目案例盘点与基于 Redux 的认证登录完整实现

Redux 实战:真实项目案例盘点与基于 Redux 的认证登录完整实现

【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux

本篇基于官方 FAQ 的Miscellaneous章节,围绕两个开发者最关心的问题展开:Redux 在真实生产环境中有哪些大型应用案例,以及如何在 Redux 中按标准模式实现用户认证。通过结合本仓库源码(src/下的中间件与工具函数实现、examples/下的官方示例),读者可以掌握认证功能的完整落地方案:Action 常量与 Action Creator 的组织、基于 Redux Thunk 的异步登录流程、token 持久化,以及 Reducer 对登录各阶段状态的响应式管理。


一、有哪些大型“真实”的 Redux 项目?

Redux 自发布以来被广泛应用于各类生产级应用。官方 FAQ 明确回答:有,而且很多。FAQ 中列举了以下代表性项目(均为官方文档所引用的历史案例,可用于了解 Redux 的应用广度):

  • Twitter 移动端网站(Twitter 的 mobile site)
  • WordPress 的新版管理后台(wp-calypso 项目)
  • Firefox 的新版调试器(Firefox DevTools debugger)
  • HyperTerm 终端应用

除了上述项目,官方 FAQ 还指出,Redux Addons Catalog 中维护了一份持续更新的Redux 应用与示例清单,收录了大量大大小小的真实应用,可供学习参考。

1.1 本仓库自带的“真实”示例:examples/

对于读者而言,与其只读名单,不如直接查看本仓库examples/目录下随 Redux 一起分发的官方示例——它们都是可运行、可复现的真实代码,覆盖了从入门到复杂的各种实战模式:

示例目录核心看点
examples/counter-vanilla不依赖构建工具与视图框架,直接以 ES5 演示最原始的 Redux API
examples/counterRedux + React 的最基础组合,包含测试
examples/todos理解 state 更新如何与组件协作:reducer 委托、容器组件生成
examples/todos-with-undoredux-undo包裹 reducer,几行代码实现撤销/重做
examples/shopping-cart规范化实体存储、多层级 reducer 组合、selector 封装、Redux Thunk 条件派发
examples/async异步 API 读取、按用户输入拉取数据、loading 指示、响应缓存与失效
examples/real-world最复杂的示例:normalizr 规范化缓存、自定义 API 中间件、分页、路由与 Redux DevTools
examples/universal服务端渲染(SSR):服务端初始化 store 状态并传递给客户端

官方 Introduction: Examples 文档对这些示例做了逐一说明。其中examples/real-world被官方称为"最先进的示例"——它展示了一个中型真实应用所需的全部关键模式,尤其值得研究的是其自定义 API 中间件(见下文第四节),这一模式与 FAQ 中"认证"章节的异步处理思路一脉相承。

说明:FAQ 中列举的 Twitter、WordPress、Firefox 等案例链接指向外部站点,出于引用规范,此处仅保留其名称与背景;若希望研究真实可运行的 Redux 代码,建议直接阅读本仓库examples/目录下的源码与配套测试。


二、如何在 Redux 中实现认证?

认证是任何真实应用都不可或缺的功能。FAQ 给出了一个非常关键的前提判断:

认证不会改变你组织应用的方式——你应当像实现任何其他功能一样来实现认证。

这句话的含义是:不要为认证单独发明一套"特殊"架构。它仍然遵循 Redux 的三条铁律(单一数据源、state 只读、用纯函数 reducer 修改 state),仍然由 action 描述"发生了什么",由 reducer 计算"下一个状态是什么"。认证状态(是否已登录、token、错误信息)只是全局 state 树中一个普通的auth分支而已。

FAQ 将实现路径归纳为四个步骤,下面逐一步骤展开,并结合仓库源码给出可直接落地的完整代码。

2.1 第一步:创建 Action 常量

为认证流程定义语义清晰的 action 类型常量,例如LOGIN_SUCCESSLOGIN_FAILURE等。在本仓库examples/async/src/actions/index.js中可以看到同样的做法——将REQUEST_POSTSRECEIVE_POSTS等字符串常量集中导出,避免拼写错误并在多个模块间复用:

// actions/auth.js —— 认证相关 action 常量 export const LOGIN_REQUEST = 'LOGIN_REQUEST' export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' export const LOGIN_FAILURE = 'LOGIN_FAILURE' export const LOGOUT = 'LOGOUT'

可以看到,我们将登录扩展成了三个阶段:LOGIN_REQUEST(请求发出)、LOGIN_SUCCESS(成功返回 token)、LOGIN_FAILURE(失败返回错误)。这正是 Redux 处理异步流程的标准三段式命名,与examples/async中的REQUEST_POSTS / RECEIVE_POSTS如出一辙。

2.2 第二步:创建 Action Creator

Action Creator 是返回 action 对象的纯函数,payload 可以是凭据(credentials)、认证是否成功的标志、token 或错误消息。本仓库 src/types/actions.ts 对ActionCreator的类型定义如下(Action 必须包含type字段):

// actions/auth.js —— action creator export const loginRequest = credentials => ({ type: LOGIN_REQUEST, credentials }) export const loginSuccess = token => ({ type: LOGIN_SUCCESS, token }) export const loginFailure = error => ({ type: LOGIN_FAILURE, error }) export const logout = () => ({ type: LOGOUT })

对比examples/async/src/actions/index.js中的requestPostsreceivePosts,结构完全一致:纯函数、返回普通对象、type 必填、其余字段按需携带数据。

2.3 第三步:创建异步 Action Creator(中间件 + 网络请求 + 持久化)

FAQ 明确指出:使用 Redux Thunk 中间件(或任何你选定的中间件)发起网络请求——若凭据有效,API 返回 token,将其保存到本地存储;若失败则向用户展示响应。这些副作用(网络请求、localStorage 写入)都可以在第二步编写的 action creator 中执行。

2.3.1 中间件在 Redux 中如何工作?

Redux 的 dispatch 是同步的,reducer 必须是纯函数,因此副作用必须放到中间件层。本仓库 src/applyMiddleware.ts 是applyMiddleware的核心实现:

export default function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer<any> { return createStore => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState) let dispatch: Dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose<typeof dispatch>(...chain)(store.dispatch) return { ...store, dispatch } } }

关键机制:

  • 每个中间件都会拿到{ getState, dispatch }两个命名参数(对应 src/types/middleware.ts 中的MiddlewareAPI接口);
  • 中间件链通过 src/compose.ts 从右到左组合,最终包裹store.dispatch
  • 中间件的签名是({ getState, dispatch }) => next => action => {...}——即:先拿到 store API,再拿到"下一个 dispatch",最后处理 action;
  • 在构造中间件期间 dispatch 是被禁用的(抛错),这是为了防止中间件尚未全部就位时就派发 action。

redux-thunk正是这种中间件的一个典型实现(源码注释在 src/applyMiddleware.ts 中直接指明:"Seeredux-thunkpackage as an example of the Redux middleware"):它检查 action 是否为函数,若是则调用它并传入dispatchgetState,从而让 action creator 可以返回函数(即 thunk)来承载异步逻辑。

2.3.2 用 Redux Thunk 编写异步登录
// api/auth.js —— 模拟认证 API const authApi = { login(credentials) { return fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }).then(response => { if (!response.ok) { return Promise.reject(new Error('用户名或密码错误')) } return response.json() // 期望返回 { token: '...' } }) } }
// actions/auth.js —— thunk action creator export const login = credentials => dispatch => { dispatch(loginRequest(credentials)) return authApi.login(credentials).then( ({ token }) => { // 将 token 持久化到本地存储(副作用放在这里) localStorage.setItem('token', token) dispatch(loginSuccess(token)) }, error => { // 失败时向用户展示错误信息 dispatch(loginFailure(error.message || '登录失败,请稍后重试')) } ) } export const logout = () => dispatch => { localStorage.removeItem('token') dispatch({ type: LOGOUT }) }

这段代码完整覆盖了 FAQ 第三步的要求:

  1. 用 Redux Thunk 让 action creator 返回函数(dispatch, getState) => {...}
  2. 内部发起网络请求;
  3. 成功则localStorage.setItem('token', token)保存 token,并派发LOGIN_SUCCESS
  4. 失败则派发LOGIN_FAILURE携带错误信息。

本仓库中与之一致的最佳实践随处可见:

  • examples/async/src/actions/index.js:fetchPosts返回dispatch => {...}的函数,先派发requestPosts,再fetch网络数据,成功后派发receivePostsfetchPostsIfNeeded更是利用(dispatch, getState)双参数在派发前检查缓存是否需要刷新;
  • examples/shopping-cart/src/actions/index.js:addToCart通过getState()读取库存后有条件地派发action——这正是认证场景中"token 是否过期、是否需要重新登录"这类判断的标准写法。
2.3.3 在 store 中挂载中间件
// store/index.js import { createStore, applyMiddleware } from 'redux' import { thunk } from 'redux-thunk' import rootReducer from './reducers' const store = createStore(rootReducer, applyMiddleware(thunk)) export default store

applyMiddleware返回的是一个store enhancer(src/applyMiddleware.ts 的多个重载签名也体现了这一点),它可以与 DevTools 等其他 enhancer 通过compose组合。参考官方 examples/real-world/src/store/configureStore.dev.js:

const store = createStore( rootReducer, preloadedState, compose(applyMiddleware(thunk, api, createLogger()), DevTools.instrument()) )

2.4 第四步:创建 Reducer 处理每一种认证状态

Reducer 是一个纯函数,接收(state, action)返回下一个 state。认证 reducer 需要为LOGIN_SUCCESSLOGIN_FAILURE等每一种情况返回对应的新状态:

// reducers/auth.js const initialState = { isAuthenticated: false, token: localStorage.getItem('token'), // 应用启动时从本地存储恢复会话 error: null } const auth = (state = initialState, action) => { switch (action.type) { case LOGIN_REQUEST: return { ...state, error: null } case LOGIN_SUCCESS: return { ...state, isAuthenticated: true, token: action.token, error: null } case LOGIN_FAILURE: return { ...state, isAuthenticated: false, error: action.error } case LOGOUT: return { ...state, isAuthenticated: false, token: null } default: return state } } export default auth

要点:

  • 不可变更新:使用对象展开运算符{ ...state, ... }生成新对象,绝不直接修改原 state;
  • default 分支必须返回原 state:对于未知 action 返回state本身;
  • 初始状态不能为undefined

examples/async/src/reducers/index.js中可以看到完全一致的 reducer 写法:posts子 reducer 分别处理INVALIDATE_SUBREDDITREQUEST_POSTSRECEIVE_POSTS三种情况,全部用展开语法返回新对象。

2.4.1 用 combineReducers 将 auth 并入根状态

认证只是应用的一个切片,最终通过combineReducers与其他 reducer 组合成根 reducer。本仓库 src/combineReducers.ts 的注释说明了其职责:把"值是 reducer 函数的对象"合并为单个 reducer,调用每个子 reducer 并将结果汇总为与键对应的 state 对象。

// reducers/index.js import { combineReducers } from 'redux' import auth from './auth' import user from './user' import posts from './posts' const rootReducer = combineReducers({ auth, user, posts }) export default rootReducer

需要注意combineReducers的两条硬性约束(见 src/combineReducers.ts 的assertReducerShape实现):

  1. 子 reducer 在初始化时(ActionTypes.INIT不得返回undefined,否则抛出异常;
  2. 用随机 action 探测时,子 reducer 对未知 action 必须返回当前 state(或初始 state),不能返回undefined。若不需要某个值,可返回null而非undefined

我们的authreducer 对未知 action 返回state,满足以上约束。

2.4.2 在组件中便捷派发:bindActionCreators

在容器组件中,可以借助 src/bindActionCreators.ts 把 action creator 自动包装成已绑定dispatch的函数,避免到处手写dispatch(login(creds))

import { bindActionCreators } from 'redux' import * as authActions from '../actions/auth' // 返回 { login: (...args) => dispatch(login(...args)), logout: (...args) => ... } const boundActions = bindActionCreators(authActions, dispatch) boundActions.login({ username, password })

源码实现说明:它会把对象中每个函数类型的值包装为dispatch(actionCreator.apply(this, args))的形式;若传入的是单个函数则直接返回包装后的函数;若传入的不是对象或函数则抛出错误(并提示是否误用了import ActionCreators from而非import * as ActionCreators from)。


三、完整链路:一次登录请求在 Redux 中如何流转

将上述四步串起来,一次login派发的完整数据流为:

  1. 组件调用login(credentials)(一个 thunk);
  2. applyMiddleware(thunk)拦截到函数类型的 action,调用它并注入dispatch/getState
  3. thunk 内部先dispatch(loginRequest(credentials)),同步派发普通 action;
  4. authreducer 收到LOGIN_REQUEST,返回{ ...state, error: null }
  5. thunk 发起fetch('/api/login')
  6. 成功后写入localStoragedispatch(loginSuccess(token));失败则dispatch(loginFailure(error))
  7. authreducer 根据 action 类型更新isAuthenticated/token/error
  8. 订阅了 store 的 UI 层通过getState().auth感知状态变化,决定渲染登录表单、加载态还是主界面。

这一"请求 → 成功/失败"的流转模式与官方 examples/async 的 fetch 流程、examples/shopping-cart 的 checkout 流程完全同构。


四、进阶:将认证请求封装为自定义中间件

FAQ 提到"使用 Redux Thunk 中间件或任何你见合适的中间件"。当应用中有大量同类异步请求时,可以学习 examples/real-world/src/middleware/api.js 的做法——把"发起请求 + 派发三阶段 action"抽象成统一的自定义中间件:

// middleware/api.js —— 借鉴 real-world 示例的三段式 API 中间件 export const CALL_API = 'Call API' const callApi = (endpoint, options) => { return fetch(endpoint, options).then(response => { if (!response.ok) { return Promise.reject(new Error(`请求失败:${response.status}`)) } return response.json() }) } export default store => next => action => { const callAPI = action[CALL_API] if (typeof callAPI === 'undefined') { return next(action) // 非 API action,直接放行 } let { endpoint } = callAPI const { types, ...rest } = callAPI // 支持以函数形式动态计算 endpoint(可读取 getState) if (typeof endpoint === 'function') { endpoint = endpoint(store.getState()) } if (typeof endpoint !== 'string') { throw new Error('必须提供字符串形式的 endpoint URL。') } if (!Array.isArray(types) || types.length !== 3) { throw new Error('types 必须是由三个 action type 组成的数组。') } const [requestType, successType, failureType] = types const actionWith = data => { const finalAction = Object.assign({}, action, data) delete finalAction[CALL_API] return finalAction } // 先派发"请求中"action next(actionWith({ type: requestType })) // 再根据结果派发"成功/失败"action return callApi(endpoint, rest).then( response => next(actionWith({ type: successType, response })), error => next(actionWith({ type: failureType, error: error.message })) ) }

这种模式的优点是把"认证请求"变成声明式的:业务侧只需描述端点与三个 action type,无需重复编写 fetch 逻辑:

export const login = credentials => ({ [CALL_API]: { endpoint: '/api/login', method: 'POST', body: JSON.stringify(credentials), types: [LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE] } })

api中间件的三层嵌套签名store => next => action => {...}与 src/types/middleware.ts 中Middleware接口的类型定义一一对应,是理解 Redux 中间件组合机制的绝佳样本。


五、安全与工程实践提醒

FAQ 没有展开,但基于仓库中的实现事实,有几点工程实践值得强调:

  • token 的存储位置:官方示例将请求结果保存到本地存储(对应 FAQ 原文 "save the token in the local storage")。在真实生产环境中,请根据安全模型权衡localStoragesessionStoragehttpOnly cookie的取舍;若存储于localStorage,注意防范 XSS 风险。
  • 应用启动时的会话恢复:如第四节 reducer 所示,可在初始 state 中读取已持久化的 token,避免刷新页面即丢失登录态。
  • 统一的错误处理LOGIN_FAILURE分支必须携带可展示的错误信息(参考 real-world 中间件中error.message || 'Something bad happened'的兜底写法)。
  • 不要改变应用组织方式:认证状态就放在auth切片中,与userposts等切片平级,通过combineReducers组合——这正是 FAQ 开篇"用实现任何其他功能的方式实现认证"的落地体现。

六、延伸阅读

  • 官方入门示例总览:Introduction: Examples
  • 本仓库随附的可运行示例源码:examples(real-world覆盖认证所需的中间件、规范化与 DevTools 全套模式)
  • 中间件设计原理:Middleware 深度解析、编写自定义中间件
  • 异步逻辑与 thunk:Fundamentals: Async Logic、Writing Logic with Thunks
  • 核心 API 源码:applyMiddleware实现见 src/applyMiddleware.ts、reducer 组合见 src/combineReducers.ts、action creator 绑定见 src/bindActionCreators.ts
  • FAQ 原文中引用的外部讨论(Reddit/HN 上的大型项目征集帖)、JWT 认证文章(Auth0)与 Redux Addons Catalog 清单,可作为背景资料按名检索,此处不再列出外部链接

【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 22:44:20

Cursor 快捷键唤不出补全?把模型通道改到 TaoToken 再试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 22:44:11

TradingAgents多智能体交易决策与LangGraph工程实践

第一次把 TradingAgents 完整跑通的那天晚上&#xff0c;我盯着终端里刷出来的十几段分析文本发了很久呆。四个分析师各自交完观点&#xff0c;多空双方来回吵了两轮&#xff0c;交易员拍了个方向&#xff0c;紧接着三个风控角色又把它从头到尾拆了一遍&#xff0c;最后汇总成一…

作者头像 李华
网站建设 2026/9/18 22:43:48

内网离线安装 Python 与 VS Code 开发环境实战指南

1. 为什么要在内网机器上死磕离线安装先说个我自己的真实经历。前年接手一个项目&#xff0c;客户的生产车间是纯物理隔离的内网&#xff0c;机器装在机柜里&#xff0c;网口都是封死的&#xff0c;连USB口都做了策略管控。当时需要在这台Windows机器上搭一套Python开发环境&am…

作者头像 李华
网站建设 2026/9/18 22:42:13

PyCharm安装配置全攻略:从解释器到虚拟环境避坑指南

我算是见过太多人把时间浪费在 PyCharm 安装配置上了。明明二十分钟能搞定的事&#xff0c;有人能折腾一整天&#xff0c;最后还装了个到处是坑的环境。不是下载了专业版找不到激活方式&#xff0c;就是装完了解释器配不上&#xff0c;一运行满屏红字。这篇东西我不打算写那种&…

作者头像 李华
网站建设 2026/9/18 22:41:29

Altium Designer导入OrCAD .DSN文件的完整技术指南

1. 项目概述&#xff1a;为什么要把.DSN文件塞进Altium Designer里&#xff1f;你手头有一份OrCAD Capture画好的原理图&#xff0c;后缀是.DSN——这玩意儿不是Altium Designer原生能打开的格式。它本质上是个数据库容器&#xff0c;里面装着.DSN主文件、.OPJ工程配置、.SCH子…

作者头像 李华