简介:react-bank是一个仅使用前端技术实现的虚拟银行模拟器项目,基于React与Next.js搭建,并融入TypeScript、Sass、HTML5/CSS3等工程实践,适合中高级前端开发者和React生态学习者作为实战参考。该项目模拟了银行常见操作场景,可作为前端状态管理与业务逻辑拆解的练习项目。资源包共39个文件,总大小仅1.88MB,核心文件包括15个tsx组件、10个scss样式、2个css、2个json以及TypeScript配置文件,组件、页面、上下文和样式分层存放。目前已有482人学习/下载,项目实现了完整的银行模拟业务规则,包括余额管理、插入余额、Pix转账、好友转账,以及银、金、铂三种账户等级计划,每种等级对应不同余额门槛和信用卡/贷款额度。通过阅读源码可以快速理解前端如何组织复杂业务状态、利用Sass编写可维护样式,以及Next.js项目的目录结构与工程化配置,适合二次开发或用于前端面试作品展示。 react-bank,光听名字就知道是什么——一个只用前端技术堆出来的虚拟银行模拟器。整个项目没有一行后端代码,账户、余额、交易流水全都存在浏览器本地,用 React 生态里最常见的路由、状态管理和本地存储方案,硬是把“银行”这个看似重型的系统模拟得有模有样。
我第一次看到这类项目的时候,第一反应是不屑:银行系统怎么可能靠前端就撑起来?但真动手做了一遍之后才发现,它的价值恰恰藏在“不可能”的背后。它把组件拆分、状态管理、路由控制、数据持久化、性能优化这些 React 开发里最容易踩坑的知识点,全部串在了一个可运行、可交互的场景里。无论你是刚学完 React 基础想找一个像样的实战项目,还是准备前端面试想把知识点串起来,react-bank 都是很好的练习载体。
接下来的内容,我会把项目设计思路先拆一遍,然后逐个说清楚核心功能的实现细节,最后分享踩过的坑和优化技巧,希望对你有用。
1. 为什么一个“银行”能只靠前端做出来
1.1 需求拆解:这个模拟器到底要模拟什么
一句话描述核心需求:在浏览器里跑一个虚拟银行,用户可以注册、登录、查看余额、存款、取款、转账,并查看历史交易记录。听起来功能不少,但落到前端实现上,本质上就是“状态管理 + 数据展示”两个话题。
我把功能拆成了三层。用户层负责注册、登录、退出登录以及当前登录用户的识别;业务层包含余额查询、存款、取款、转账、交易记录;数据层负责把用户信息和交易记录持久化到浏览器本地,扮演后端数据库的角色。
这个分层思路和真实后端开发几乎一致,只是我们不需要写服务器代码,也不需要建表,LocalStorage 就充当了数据落盘的角色。把一个复杂系统简化成前端可运行的形式,同时保留核心业务规则,这就是模拟器项目存在的意义。
1.2 技术选型:React 与 LocalStorage 的组合逻辑
选 React 而不是 Vue,原因很直接:React 的单向数据流和组件化非常适合这种业务规则明确、状态相互关联的场景。页面组件只负责渲染,账户状态统一放在全局 Store 里管理,数据流向清楚,排查问题更容易。
不用后端也不是偷懒,是有意做的减法。模拟器的核心目标是展示前端能力,如果把 Node.js、数据库全拉进来,学习成本会大幅上升,反而冲淡了“前端如何组织复杂状态”这个命题。LocalStorage 作为持久化方案足够用,读写简单、API 稳定、容量约 5MB,存一个虚拟银行模拟器的用户数据和交易记录完全够用。
当然有个前提:如果交易记录特别多,LocalStorage 同步读写会拖慢页面,项目后期可以换成 IndexedDB。但初期 LocalStorage 就是性价比最高的选择,能让你把精力聚焦在 React 本身。
最后再强调一下,这个项目是纯粹的教学模拟器,不涉及真实资金,也不代表任何银行系统,所有数据只存在于你的浏览器里。
2. react-bank 核心架构设计:状态、数据与组件树怎么组织
2.1 组件树与状态管理方案
组件结构按页面维度拆分:登录页、注册页、账户主页、交易页、交易记录页,加上余额卡片、交易列表、操作按钮等公共组件。页面之间通过路由隔离,状态用全局 Store 统一管理。
状态管理我选了 Context + useReducer 的组合,没有引入 Redux。账户对象包含用户名、账户号、余额、创建时间;交易记录包含交易类型、金额、交易对象、时间、剩余余额。这两块数据是整个系统的“命脉”。
为什么用 useReducer 而不是一堆 useState?因为账户和交易操作不是简单赋值,而是有前后依赖的“事务”。举个例子,转账要同时扣减当前用户余额、增加对方余额、生成一条交易记录。用 useReducer 可以把这个流程收敛到一个 reducer 里,保证数据变更的顺序和一致性。如果每个状态都单独 useState,很容易出现“扣了钱但对方没收到”的脏数据。
2.2 React 18 批处理机制:自动合并带来的性能红利
React 18 的批处理(Batching)是这个项目里最值得讲透的知识点。简单说,批处理就是把多个 setState 合并成一次重新渲染,避免频繁调用渲染函数造成性能浪费。
React 18 之前,只有事件处理函数里的 setState 会被自动批处理。比如点击事件里连续 setState 三次,只会触发一次渲染。但 Promise 回调、setTimeout、原生事件处理器里,React 17 及之前不会自动批处理,每调一次 setState 就渲染一次,性能明显下降。
React 18 把所有场景都纳入了自动批处理。我在模拟器里做了一个典型场景:转账时更新当前用户余额、更新对方余额、插入交易记录、刷新总资产,这四个 dispatch 放在同一个提交函数里,React 18 自动合并成一次渲染。用户感知就是“点一下按钮,页面一次性刷新完成”。没有批处理的话,你会看到余额数字跳好几次,观感很差。
这里放一个我在项目里实际写过的 reducer 片段,帮助理解:
function accountReducer(state, action) { switch (action.type) { case 'transfer': if (state.balance < action.payload.amount) { return state; // 余额不足,直接拒绝 } const nextBalance = state.balance - action.payload.amount; return { ...state, balance: nextBalance, transactions: [ ...state.transactions, { type: 'transfer_out', amount: action.payload.amount, to: action.payload.to, createdAt: new Date().toISOString(), }, ], }; default: return state; } }如果业务确实需要强制同步渲染,React 18 提供了 flushSync API,但我在项目里很少用。日常开发里,能让它批处理就让,不要手动去拆分更新,这是和 React 18 合作的最好姿势。
3. react-bank 核心功能实现:账户、交易与持久化全流程
3.1 注册登录与前端路由守卫
账户模块是模拟器的入口。注册页需要用户名和密码,前端校验用户名不能为空、密码长度不少于 6 位,校验通过后生成唯一账户号(我用时间戳加随机数拼接),然后把用户信息写进 LocalStorage。
这里要特别说明:模拟器里密码是明文存储的,但这是我故意做的简化,因为项目只用于学习演示。真实项目里密码绝不允许明文存储,必须走服务端哈希加密。我甚至在代码注释里都写了同样的提醒,防止将来有人把这个 Demo 直接当生产项目用。
登录逻辑分为两步:读取 LocalStorage 用户列表,比对用户名和密码;登录成功后把当前用户名存进 Context,同时写一个 session 标记。后续所有页面通过 Context 判断是否登录,未登录就重定向到登录页,这就是前端层面的路由守卫。
3.2 存款、取款、转账的边界处理
交易模块是模拟器里最容易出 BUG 的地方。存款和取款相对简单:存款往余额上加钱并记录一笔“存款”交易;取款先校验余额是否足够,不够就提示“余额不足”。
转账是最复杂的业务场景。用户输入对方账户号和转账金额后,需要依次完成:校验转账金额大于 0、校验当前用户余额足够、扣减当前用户余额、增加对方账户余额、生成转账交易记录。我把它写在一个提交函数里,任何校验失败就提前返回,绝不允许走到“扣钱但对方没收到”的分支。
这里还有一个特别容易忽略的点:重复点击。我专门在提交函数里加了 isSubmitting 状态,提交期间按钮置灰并使用防重复标志锁住操作,防止用户在转账过程中疯狂点击按钮,触发多次转账。这个防护在真实支付系统里是标配,前端做模拟器时也算得上一项基本功。
3.3 数据持久化方案与版本化存储
我把 LocalStorage 的读写封装成了一个 storage 工具,核心方法就三个:get、set、remove。写入时 JSON.stringify,读取时 JSON.parse,并且做好容错处理,解析失败就返回空对象,而不是抛出异常。
存储 key 我带了版本号,例如 react_bank_users_v1、react_bank_transactions_v1。这样以后如果数据结构调整,可以做迁移,而不是直接读旧数据。虽然模拟器用不到太复杂的迁移逻辑,但这个习惯延伸到了真实项目里,帮我避免过好几次“上线后老用户数据全废”的麻烦。
一个小细节:交易记录属于高频写入数据,我选择了“合并写入”策略——每次交易完成后把用户数据和交易记录一次性 set 进 LocalStorage,避免频繁调用 storage API。这样可以减少序列化和 I/O 操作带来的卡顿。
4. 性能优化实录:让模拟器在高数据量下保持流畅
4.1 自动批处理对渲染频率的实际改善
第二章讲了批处理原理,这里说下实测效果。我做了个对比实验:在交易记录页渲染 500 条流水数据时,分别用“逐条 setState”和“批量更新”两种写法提交同样的数据,观察渲染耗时的差异。使用批处理后,从数据变更到渲染完成的时间明显缩短,在低端设备上差距更为显著。
这个结果并不意外。React 每次渲染都要经历创建虚拟 DOM、对比差异、提交真实 DOM 的过程,渲染频率越高,开销就越大。批处理把多次更新合并成一次渲染,等于直接把高频操作变成了低频操作。
所以在模拟器里,我刻意把所有交易逻辑集中在一个提交函数里,让状态更新进入同一轮批处理。这个习惯哪怕在将来 React 版本升级后也适用,因为你始终遵循了“一次交互只发起一轮更新”的原则。
4.2 组件缓存、key 选择与虚拟滚动思路
交易记录列表是数据量最大的部分。我用了 React.memo 包裹列表项组件,让列表项只在 props 变化时才重新渲染。同时用 useMemo 缓存时间戳格式化函数的结果,避免每次渲染都做无意义的计算。
key 的选择也很关键。交易记录列表中,我坚持使用交易 ID 作为 key,而不是数组下标。虽然当前场景里交易记录没有中间插入操作,看不出明显区别,但一旦以后支持删除、排序,key 为下标会导致 React 复用错误组件状态,出现“列表显示错乱、输入框内容串位”这类诡异问题。这是我见过的高频 BUG。
如果交易记录量级继续增大,比如上万条,虚拟滚动是更合理的方案,只渲染可视区域内的列表项。我在模拟器里没有引入第三方虚拟滚动库,但我建议实际项目中用 react-window 或 react-virtualized。这也是前端性能优化面试题里的常客。
5. 实测避坑:5 个高频问题排查与解决
5.1 状态更新后页面不刷新
我在开发取款功能时遇到过:余额确实被扣了,但页面纹丝不动。排查后发现,reducer 里直接修改了原 state 的 balance 字段再返回原对象,React 通过引用比较判断状态没有变化,于是跳过渲染。
解决方案很明确:创建新对象返回,保证 state 引用变化。这也是 React 不可变数据原则的核心。遇到“状态变了但页面不动”的问题,先检查 reducer 有没有遵守这个原则,八成的坑都在这里。
5.2 重复点击转账导致余额变负
手速快的用户会连续点击转账按钮,如果前端没有操作锁,余额可能被扣成负数。我在提交函数里用 isSubmitting 状态锁定按钮,同时在 reducer 里又做了一次余额校验,双保险。
要注意的是,前端防重复提交只是第一道防线,真实生产环境里还需要后端幂等和分布式锁。但在模拟器项目里,前端这层防护足够说明问题了。
5.3 刷新后登录态丢失
Context 里的登录状态只存在于内存,刷新就没了。我的处理方式是在 LocalStorage 里保存一个 session 标记,刷新后通过初始化逻辑读取标记,重新设置 Context。
这里要留意 session 过期时间。我的模拟器比较简单,没有做过期处理,但如果接入真实场景,建议给 session 标记加一个 timestamp,超过一定时间就要求重新登录,避免用户无限期保持会话。
5.4 部署后路由刷新 404
本地开发一切正常,部署到静态服务器后,直接访问 /account 路径却报 404。这是因为开发环境由 webpack-dev-server 处理 history 回退,生产环境静态服务器并不知道前端路由的存在。
解决方案是在服务器层把不存在的路径全部重写回 index.html,交给前端路由接管。Nginx 写法是 try_files 指令,Node 部署可以加一个 fallback 中间件。思路都一样:所有路径统一交给 index.html,前端路由再决定展示哪个页面。
做完 react-bank 之后我最大的体会是:前端开发里很多概念,背得再熟,不如亲手写一遍。这个模拟器表面功能不复杂,但它逼着我把状态管理、路由守卫、数据持久化、性能优化这些知识点全部串在一起,尤其是 React 18 的批处理机制,不在这种带状态依赖的业务场景里实际跑一遍,很难真正体会到它带来的性能改善。
最后分享一个小习惯:每完成一个模块,我会打开浏览器的 Performance 面板跑一次核心操作,记录渲染耗时,再用 React DevTools 检查组件有没有多余的重渲染。这个习惯让我在项目早期就发现了多处隐患。如果你也在做类似的 React 练习项目,建议你也试试。
本文还有配套的精品资源,点击获取