ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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 siteWordPress 的新版管理后台wp-calypso 项目Firefox 的新版调试器Firefox DevTools debuggerHyperTerm 终端应用除了上述项目官方 FAQ 还指出Redux Addons Catalog 中维护了一份持续更新的Redux 应用与示例清单收录了大量大大小小的真实应用可供学习参考。1.1 本仓库自带的“真实”示例examples/对于读者而言与其只读名单不如直接查看本仓库examples/目录下随 Redux 一起分发的官方示例——它们都是可运行、可复现的真实代码覆盖了从入门到复杂的各种实战模式示例目录核心看点examples/counter-vanilla不依赖构建工具与视图框架直接以 ES5 演示最原始的 Redux APIexamples/counterRedux React 的最基础组合包含测试examples/todos理解 state 更新如何与组件协作reducer 委托、容器组件生成examples/todos-with-undo用redux-undo包裹 reducer几行代码实现撤销/重做examples/shopping-cart规范化实体存储、多层级 reducer 组合、selector 封装、Redux Thunk 条件派发examples/async异步 API 读取、按用户输入拉取数据、loading 指示、响应缓存与失效examples/real-world最复杂的示例normalizr 规范化缓存、自定义 API 中间件、分页、路由与 Redux DevToolsexamples/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_SUCCESS、LOGIN_FAILURE等。在本仓库examples/async/src/actions/index.js中可以看到同样的做法——将REQUEST_POSTS、RECEIVE_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 CreatorAction 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中的requestPosts、receivePosts结构完全一致纯函数、返回普通对象、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[]): StoreEnhancerany { 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 composetypeof 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 是否为函数若是则调用它并传入dispatch与getState从而让 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 第三步的要求用 Redux Thunk 让 action creator 返回函数(dispatch, getState) {...}内部发起网络请求成功则localStorage.setItem(token, token)保存 token并派发LOGIN_SUCCESS失败则派发LOGIN_FAILURE携带错误信息。本仓库中与之一致的最佳实践随处可见examples/async/src/actions/index.jsfetchPosts返回dispatch {...}的函数先派发requestPosts再fetch网络数据成功后派发receivePostsfetchPostsIfNeeded更是利用(dispatch, getState)双参数在派发前检查缓存是否需要刷新examples/shopping-cart/src/actions/index.jsaddToCart通过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 storeapplyMiddleware返回的是一个store enhancersrc/applyMiddleware.ts 的多个重载签名也体现了这一点它可以与 DevTools 等其他 enhancer 通过compose组合。参考官方 examples/real-world/src/store/configureStore.dev.jsconst store createStore( rootReducer, preloadedState, compose(applyMiddleware(thunk, api, createLogger()), DevTools.instrument()) )2.4 第四步创建 Reducer 处理每一种认证状态Reducer 是一个纯函数接收(state, action)返回下一个 state。认证 reducer 需要为LOGIN_SUCCESS、LOGIN_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, ... }生成新对象绝不直接修改原 statedefault 分支必须返回原 state对于未知 action 返回state本身初始状态不能为undefined。在examples/async/src/reducers/index.js中可以看到完全一致的 reducer 写法posts子 reducer 分别处理INVALIDATE_SUBREDDIT、REQUEST_POSTS、RECEIVE_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实现子 reducer 在初始化时ActionTypes.INIT不得返回undefined否则抛出异常用随机 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派发的完整数据流为组件调用login(credentials)一个 thunkapplyMiddleware(thunk)拦截到函数类型的 action调用它并注入dispatch/getStatethunk 内部先dispatch(loginRequest(credentials))同步派发普通 actionauthreducer 收到LOGIN_REQUEST返回{ ...state, error: null }thunk 发起fetch(/api/login)成功后写入localStorage并dispatch(loginSuccess(token))失败则dispatch(loginFailure(error))authreducer 根据 action 类型更新isAuthenticated/token/error订阅了 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。在真实生产环境中请根据安全模型权衡localStorage、sessionStorage或httpOnly cookie的取舍若存储于localStorage注意防范 XSS 风险。应用启动时的会话恢复如第四节 reducer 所示可在初始 state 中读取已持久化的 token避免刷新页面即丢失登录态。统一的错误处理LOGIN_FAILURE分支必须携带可展示的错误信息参考 real-world 中间件中error.message || Something bad happened的兜底写法。不要改变应用组织方式认证状态就放在auth切片中与user、posts等切片平级通过combineReducers组合——这正是 FAQ 开篇用实现任何其他功能的方式实现认证的落地体现。六、延伸阅读官方入门示例总览Introduction: Examples本仓库随附的可运行示例源码examplesreal-world覆盖认证所需的中间件、规范化与 DevTools 全套模式中间件设计原理Middleware 深度解析、编写自定义中间件异步逻辑与 thunkFundamentals: Async Logic、Writing Logic with Thunks核心 API 源码applyMiddleware实现见 src/applyMiddleware.ts、reducer 组合见 src/combineReducers.ts、action creator 绑定见 src/bindActionCreators.tsFAQ 原文中引用的外部讨论Reddit/HN 上的大型项目征集帖、JWT 认证文章Auth0与 Redux Addons Catalog 清单可作为背景资料按名检索此处不再列出外部链接【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表